1. Go to this page and download the library: Download coroq/form library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
coroq / form example snippets
use Coroq\Form\Form;
use Coroq\Form\FormItem\EmailInput;
use Coroq\Form\FormItem\TextInput;
class LoginForm extends Form {
public readonly EmailInput $email;
public readonly TextInput $password;
public function __construct() {
$this->email = new EmailInput();
$this->password = new TextInput();
}
}
$form = new LoginForm();
$form->setValue($_POST);
if ($form->validate()) {
$email = $form->email->getEmail();
// Process login...
} else {
$errors = $form->getError();
// Handle validation errors
}
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
use Coroq\Form\FormItem\EmailInput;
use Coroq\Form\FormItem\IntegerInput;
use Coroq\Form\FormItem\Select;
class UserRegistrationForm extends Form {
public readonly TextInput $name;
public readonly EmailInput $email;
public readonly IntegerInput $age;
public readonly Select $country;
public function __construct() {
$this->name = (new TextInput())
->setLabel('Name')
->setMaxLength(100);
$this->email = (new EmailInput())
->setLabel('Email');
$this->age = (new IntegerInput())
->setLabel('Age')
->setMin(18)
->setMax(120);
$this->country = (new Select())
->setLabel('Country')
->setOptions([
'us' => 'United States',
'jp' => 'Japan',
'uk' => 'United Kingdom'
]);
}
}
// Usage with full IDE support
$form = new UserRegistrationForm();
$form->setValue($_POST);
if ($form->validate()) {
// IDE knows the exact types
$name = $form->name->getValue();
$email = $form->email->getEmail();
$age = $form->age->getInteger();
}
use Coroq\Form\Form;
use Coroq\Form\FormItem\EmailInput;
use Coroq\Form\FormItem\TextInput;
$form = new Form();
$form->email = new EmailInput();
$form->name = new TextInput();
$form->setValue($_POST);
$form->validate();
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
class ProfileForm extends Form {
public readonly TextInput $name;
public readonly TextInput $nickname;
public function __construct() {
$this->name = new TextInput(); // Required (default)
$this->nickname = (new TextInput())
->setRequired(false); // Optional
}
}
$form = new ProfileForm();
$form->setValue(['name' => '', 'nickname' => '']);
$form->validate();
// name has EmptyError, nickname passes validation
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
class AddressForm extends Form {
public readonly TextInput $street;
public readonly TextInput $city;
public function __construct() {
$this->street = new TextInput();
$this->city = new TextInput();
$this->setRequired(false); // Make entire form optional
}
}
$form = new AddressForm();
$form->setValue(['street' => '', 'city' => '']);
$form->validate(); // Passes! Empty optional form skips item validation
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
class UserForm extends Form {
public readonly TextInput $id;
public readonly TextInput $name;
public function __construct() {
$this->id = (new TextInput())
->setValue('12345')
->setReadOnly(true);
$this->name = new TextInput();
}
}
$form = new UserForm();
$form->setValue(['id' => '99999', 'name' => 'Taro']);
echo $form->id->getValue(); // "12345" (unchanged)
echo $form->name->getValue(); // "Taro"
$form->validate(); // Both items are validated
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
class DisplayForm extends Form {
public readonly TextInput $field;
public function __construct() {
$this->field = (new TextInput())->setValue('fixed');
$this->setReadOnly(true); // Entire form is read-only
}
}
$form = new DisplayForm();
$form->setValue(['field' => 'new value']); // Ignored!
echo $form->field->getValue(); // "fixed"
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
class OrderForm extends Form {
public readonly TextInput $customerName;
public readonly TextInput $legacyField;
public function __construct() {
$this->customerName = new TextInput();
$this->legacyField = (new TextInput())
->setDisabled(true);
}
}
$form = new OrderForm();
$form->setValue([
'customerName' => 'Taro',
'legacyField' => 'some value'
]);
$values = $form->getValue();
// ['customerName' => 'Taro']
// legacyField is excluded from getValue()
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
class CheckoutForm extends Form {
public readonly TextInput $name;
public readonly AddressForm $billing;
public readonly AddressForm $shipping;
public function __construct() {
$this->name = new TextInput();
$this->billing = new AddressForm();
$this->shipping = new AddressForm();
}
public function disableShipping() {
$this->shipping->setDisabled(true);
return $this;
}
}
$form = new CheckoutForm();
$form->disableShipping();
$form->setValue([
'name' => 'Taro',
'billing' => ['street' => '1-1-1', 'city' => 'Tokyo'],
'shipping' => ['street' => '2-2-2', 'city' => 'Osaka']
]);
$values = $form->getValue();
// ['name' => 'Taro', 'billing' => ['street' => '1-1-1', 'city' => 'Tokyo']]
// shipping is excluded from getValue()
use Coroq\Form\Form;
use Coroq\Form\FormItem\EmailInput;
use Coroq\Form\FormItem\IntegerInput;
class LoginForm extends Form {
public readonly EmailInput $email;
public readonly IntegerInput $age;
public function __construct() {
$this->email = new EmailInput();
$this->age = (new IntegerInput())->setMin(18);
}
}
$form = new LoginForm();
$form->setValue([
'email' => 'invalid-email',
'age' => '15'
]);
if ($form->validate()) {
// All valid
} else {
// Check individual fields
if ($form->email->hasError()) {
$error = $form->email->getError();
echo get_class($error); // "Coroq\Form\Error\InvalidEmailError"
}
if ($form->age->hasError()) {
$error = $form->age->getError();
echo get_class($error); // "Coroq\Form\Error\TooSmallError"
}
// Get all errors at once
$errors = $form->getError();
// ['email' => InvalidEmailError, 'age' => TooSmallError]
}
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
use Coroq\Form\Error\InvalidError;
class RegistrationForm extends Form {
public readonly TextInput $username;
public function __construct() {
$this->username = (new TextInput())
->setMinLength(3)
->setValidator(function($formItem, $value) {
// Additional validation: no special characters
if (preg_match('/[^a-z0-9_]/', $value)) {
return new InvalidError($formItem);
}
return null;
});
}
}
use Coroq\Form\FormItem\EmailInput;
use Coroq\Form\Error\InvalidError;
$email = (new EmailInput())
->setValidator(function($formItem, $value) {
// Block disposable email domains
if (str_ends_with($value, '@tempmail.com')) {
return new InvalidError($formItem);
}
return null;
});
$email->setValue('[email protected]');
$email->validate(); // Fails - custom validator returns error
$email->setValue('invalid-email');
$email->validate(); // Fails - built-in email validation fails first
// Custom validator never runs
use Coroq\Form\FormItem\IntegerInput;
use Coroq\Form\Error\InvalidError;
$quantity = (new IntegerInput())
->setMin(1)
->setMax(100)
->setValidator(function($formItem, $value) {
// Reject quantities not divisible by 5
if ((int)$value % 5 !== 0) {
return new InvalidError($formItem);
}
return null;
});
use Coroq\Form\Form;
use Coroq\Form\FormItem\EmailInput;
use Coroq\Form\FormItem\TextInput;
use Coroq\Form\FormItem\Input;
use Coroq\Form\Error\InvalidError;
class LoginForm extends Form {
public readonly EmailInput $email;
public readonly TextInput $password;
public readonly Input $authResult;
public function __construct() {
$this->email = new EmailInput();
$this->password = new TextInput();
$this->authResult = (new Input())->setReadOnly(true);
}
}
$form = new LoginForm();
$form->setValue($_POST);
if ($form->validate()) {
// External validation
if (!$authService->authenticate($form->email->getValue(), $form->password->getValue())) {
$form->authResult->setError(new InvalidError($form->authResult));
}
}
if ($form->hasError()) {
// Handle all errors uniformly
}
use Coroq\Form\Form;
use Coroq\Form\FormItem\BooleanInput;
use Coroq\Form\FormItem\TextInput;
class RegistrationForm extends Form {
public readonly BooleanInput $isCompany;
public readonly TextInput $companyName;
public readonly TextInput $division;
public function __construct() {
$this->isCompany = new BooleanInput();
$this->companyName = new TextInput();
$this->division = new TextInput();
}
public function setValue(mixed $value): self {
parent::setValue($value);
// Conditional validation: enable company fields only if isCompany is true
$isCompany = $this->isCompany->getBoolean();
$this->companyName->setDisabled(!$isCompany);
$this->division->setDisabled(!$isCompany);
return $this;
}
}
$form = new RegistrationForm();
$form->setValue(['isCompany' => 'on', 'companyName' => '', 'division' => '']);
$form->validate();
// companyName and division have EmptyError (enabled and
class CheckoutForm extends Form {
public readonly BillingForm $billing;
public readonly ShippingForm $shipping;
public function setValue(mixed $value): self {
parent::setValue($value);
// Disable shipping section if same as billing
$this->shipping->setDisabled($this->billing->sameAsShipping->getBoolean());
return $this;
}
}
use Coroq\Form\FormItem\BooleanInput;
use Coroq\Form\Error\Error;
use Coroq\Form\Error\EmptyError;
class NoAgreementError extends Error {}
$agree = (new BooleanInput())
->setRequired(true)
->setErrorCustomizer(function(Error $error, $formItem): Error {
if ($error instanceof EmptyError) {
return new NoAgreementError($formItem);
}
return $error;
});
$agree->validate();
echo get_class($agree->getError()); // "NoAgreementError"
use Coroq\Form\ErrorMessageFormatter;
use Coroq\Form\Error\EmptyError;
use Coroq\Form\Error\InvalidError;
use Coroq\Form\Error\TooLongError;
use Coroq\Form\Error\TooSmallError;
// Define your message set
$messages = [
EmptyError::class => 'This field is );
// Format errors
$form->validate();
if ($form->email->hasError()) {
echo $formatter->format($form->email->getError());
// "Invalid value" (InvalidEmailError extends InvalidError)
}
use Coroq\Form\ErrorMessageFormatter;
use Coroq\Form\Error\InvalidError;
use Coroq\Form\Error\InvalidEmailError;
$messages = [
InvalidError::class => 'Invalid value', // Base message for all Invalid* errors
InvalidEmailError::class => 'Please enter a valid email address', // Specific override
];
$formatter = new ErrorMessageFormatter();
$formatter->setMessages($messages);
// InvalidEmailError → 'Please enter a valid email address' (specific)
// InvalidUrlError → 'Invalid value' (falls back to base)
// InvalidDateError → 'Invalid value' (falls back to base)
// Start with preset base messages
$messages = [
EmptyError::class => 'This field is ',
TooSmallError::class => 'Value is too small',
];
// Add specific overrides
$messages = [
...$messages, // Base messages
InvalidEmailError::class => 'Please enter a valid email address',
TooLongError::class => fn($e) => "Maximum {$e->formItem->getMaxLength()} characters",
];
$formatter = new ErrorMessageFormatter();
$formatter->setMessages($messages);
$formatter = new ErrorMessageFormatter();
// Set base messages
$formatter->setMessages([
EmptyError::class => 'Required field',
InvalidError::class => 'Invalid value',
]);
// Add or override specific messages
$formatter->setMessage(InvalidEmailError::class, 'Please enter a valid email');
$formatter->setMessage(TooLongError::class, fn($e) => "Max {$e->formItem->getMaxLength()} chars");
use Coroq\Form\ErrorMessageFormatter;
use Coroq\Form\Error\EmptyError;
use Coroq\Form\Error\TooLongError;
use Coroq\Form\Error\TooSmallError;
$messages = [
EmptyError::class => function(EmptyError $error) {
return $error->formItem->getLabel() . ' is r->formItem->getMin();
},
];
$formatter = new ErrorMessageFormatter();
$formatter->setMessages($messages);
use Coroq\Form\Error\Error;
use Coroq\Form\FormItem\FormItemInterface;
// Define custom error
class PasswordMismatchError extends Error {
/** @property-read PasswordInput $formItem */
}
class RateLimitError extends Error {
public function __construct(
FormItemInterface $formItem,
public readonly int $remainingSeconds
) {
parent::__construct($formItem);
}
}
// Use in messages
$messages = [
PasswordMismatchError::class => 'Passwords do not match',
RateLimitError::class => function(RateLimitError $error) {
return 'Too many attempts. Try again in ' . $error->remainingSeconds . ' seconds';
},
];
use Coroq\Form\Form;
use Coroq\Form\FormItem\EmailInput;
use Coroq\Form\FormItem\IntegerInput;
use Coroq\Form\FormItem\BooleanInput;
use Coroq\Form\FormItem\TextInput;
class UserForm extends Form {
public readonly EmailInput $email;
public readonly IntegerInput $age;
public readonly BooleanInput $newsletter;
public readonly TextInput $notes;
public function __construct() {
$this->email = new EmailInput();
$this->age = (new IntegerInput())->setRequired(false);
$this->newsletter = (new BooleanInput())->setRequired(false);
$this->notes = (new TextInput())->setRequired(false);
}
}
$form = new UserForm();
$form->setValue([
'email' => '[email protected]',
'age' => '25',
'newsletter' => 'on',
'notes' => ''
]);
// getValue() - raw strings,
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
use Coroq\Form\FormItem\UnicodeNormalization;
class ProfileForm extends Form {
public readonly TextInput $name;
public readonly TextInput $bio;
public function __construct() {
$this->name = (new TextInput())
->setMinLength(2)
->setMaxLength(100)
->setTrim(TextInput::BOTH) // LEFT, RIGHT, BOTH, or null
->setCase(TextInput::TITLE) // UPPER, LOWER, TITLE
->setMb('KV') // mb_convert_kana option
->setPattern('/^[A-Za-z ]+$/'); // Regex validation
$this->bio = (new TextInput())
->setMultiline(true)
->setEol("\n") // Normalize line endings
->setMaxLength(1000);
}
}
use Coroq\Form\FormItem\UnicodeNormalization;
// Default: NFC if intl available, otherwise no normalization
$input = new TextInput();
// Use different form (NFD, NFKC, NFKD)
$input->setUnicodeNormalization(UnicodeNormalization::NFKC);
// Disable normalization
$input->setUnicodeNormalization(null);
use Coroq\Form\Form;
use Coroq\Form\FormItem\EmailInput;
class ContactForm extends Form {
public readonly EmailInput $email;
public function __construct() {
$this->email = new EmailInput();
// Note: setLowerCaseDomain(true) is also default
}
}
$form = new ContactForm();
$form->email->setValue('[email protected]');
echo $form->email->getValue(); // "[email protected]"
echo $form->email->getEmail(); // "[email protected]" or null if invalid
use Coroq\Form\Form;
use Coroq\Form\FormItem\UrlInput;
class ProfileForm extends Form {
public readonly UrlInput $website;
public function __construct() {
$this->website = new UrlInput();
}
}
$form = new ProfileForm();
$form->website->setValue('https://example.com/path?query=value');
$form->validate(); // true
echo $form->website->getUrl(); // "https://example.com/path?query=value"
// Invalid URL
$form->website->setValue('not a url');
$form->validate(); // false - InvalidUrlError
use Coroq\Form\Form;
use Coroq\Form\FormItem\TelInput;
class ContactForm extends Form {
public readonly TelInput $phone;
public function __construct() {
$this->phone = new TelInput();
}
}
$form = new ContactForm();
// International format (E.164)
$form->phone->setValue('+81-90-1234-5678');
echo $form->phone->getValue(); // "+819012345678" (E.164 format)
// Domestic format
$form->phone->setValue('090-1234-5678');
echo $form->phone->getValue(); // "09012345678" (domestic, digits only)
use Coroq\Form\FormItem\TelInput;
use libphonenumber\PhoneNumberUtil;
use libphonenumber\PhoneNumberFormat;
$phone = new TelInput();
$phone->setValue('+81-90-1234-5678');
echo $phone->getValue(); // "+819012345678"
// For validation/formatting, use libphonenumber (giggsey/libphonenumber-for-php)
$phoneUtil = PhoneNumberUtil::getInstance();
// Parse with country hint for domestic numbers
$number = $phoneUtil->parse($phone->getValue(), 'JP');
// Or parse E.164 directly (no country hint needed)
$number = $phoneUtil->parse('+819012345678');
// Format for display
$formatted = $phoneUtil->format($number, PhoneNumberFormat::NATIONAL);
// "090-1234-5678"
use Coroq\Form\Form;
use Coroq\Form\FormItem\Select;
class SettingsForm extends Form {
public readonly Select $country;
public function __construct() {
$this->country = (new Select())
->setOptions([
'us' => 'United States',
'jp' => 'Japan',
'uk' => 'United Kingdom'
]);
}
}
$form = new SettingsForm();
$form->country->setValue('jp');
echo $form->country->getValue(); // "jp"
echo $form->country->getSelectedLabel(); // "Japan"
use Coroq\Form\Form;
use Coroq\Form\FormItem\MultiSelect;
class SurveyForm extends Form {
public readonly MultiSelect $hobbies;
public function __construct() {
$this->hobbies = (new MultiSelect())
->setOptions([
'sports' => 'Sports',
'music' => 'Music',
'reading' => 'Reading',
'gaming' => 'Gaming'
])
->setMinCount(1)
->setMaxCount(3);
}
}
$form = new SurveyForm();
$form->hobbies->setValue(['sports', 'music']);
print_r($form->hobbies->getValue()); // ['sports', 'music']
print_r($form->hobbies->getSelectedLabel()); // ['Sports', 'Music']
use Coroq\Form\Form;
use Coroq\Form\FormItem\NumberInput;
use Coroq\Form\FormItem\IntegerInput;
class ProductForm extends Form {
public readonly NumberInput $price;
public readonly IntegerInput $quantity;
public function __construct() {
$this->price = (new NumberInput())
->setMin(0.01)
->setMax(999999.99);
$this->quantity = (new IntegerInput())
->setMin(1)
->setMax(100);
}
}
$form = new ProductForm();
$form->price->setValue('123.45'); // Full-width input
echo $form->price->getValue(); // "123.45" (normalized)
echo $form->price->getNumber(); // 123.45 (float)
echo $form->quantity->getInteger(); // 42 or null
use Coroq\Form\Form;
use Coroq\Form\FormItem\DateInput;
class EventForm extends Form {
public readonly DateInput $eventDate;
public function __construct() {
$this->eventDate = new DateInput();
}
}
$form = new EventForm();
$form->eventDate->setValue('2000/1/15');
echo $form->eventDate->getValue(); // "2000-01-15" (normalized)
$dt = $form->eventDate->getDateTime(); // DateTime object or null
$dti = $form->eventDate->getDateTimeImmutable(); // DateTimeImmutable or null
use Coroq\Form\Form;
use Coroq\Form\FormItem\BooleanInput;
class RegistrationForm extends Form {
public readonly BooleanInput $agreeToTerms;
public readonly BooleanInput $newsletter;
public function __construct() {
// Required boolean - user must accept (value must be truthy)
$this->agreeToTerms = new BooleanInput();
// Optional boolean - can be true or false
$this->newsletter = (new BooleanInput())
->setRequired(false);
}
}
$form = new RegistrationForm();
// User didn't check the checkbox (empty/false)
$form->setValue(['agreeToTerms' => '', 'newsletter' => '']);
$form->validate(); // FAILS - agreeToTerms is
use Coroq\Form\Form;
use Coroq\Form\FormItem\FileInput;
class UploadForm extends Form {
public readonly FileInput $avatar;
public readonly FileInput $document;
public function __construct() {
// Image upload with size and type restrictions
$this->avatar = (new FileInput())
->setRequired(false) // Usually optional
->setMaxSize(5 * 1024 * 1024) // 5 MB
->setAllowedMimeTypes(['image/jpeg', 'image/png', 'image/gif'])
->setAllowedExtensions(['jpg', 'jpeg', 'png', 'gif']);
// Document upload
$this->document = (new FileInput())
->setRequired(false)
->setMaxSize(10 * 1024 * 1024) // 10 MB
->setMinSize(1024) // 1 KB minimum
->setAllowedMimeTypes(['application/pdf'])
->setAllowedExtensions(['pdf']);
}
}
// Your HTTP layer moves uploaded file to temporary storage
$tempPath = '/app/storage/temp/' . uniqid() . '.jpg';
move_uploaded_file($_FILES['avatar']['tmp_name'], $tempPath);
// FileInput validates the file at the path
$form = new UploadForm();
$form->avatar->setValue($tempPath);
if ($form->validate()) {
$filePath = $form->avatar->getValue();
// Move to permanent storage, save file ID, etc.
}
use Coroq\Form\Form;
use Coroq\Form\FormItem\FileInput;
use Coroq\Form\FormItem\TextInput;
class ProfileForm extends Form {
public readonly FileInput $newAvatar; // Optional - for new uploads
public readonly TextInput $avatarId; // Required - tracks saved file
}
// First submit: user uploads new file
if ($_FILES['newAvatar']['tmp_name']) {
$tempPath = moveToTempStorage($_FILES['newAvatar']);
$form->newAvatar->setValue($tempPath);
}
if ($form->validate()) {
if ($form->newAvatar->getValue()) {
// Save new file and get ID
$avatarId = $storage->save($form->newAvatar->getValue());
$form->avatarId->setValue($avatarId);
}
}
// Resubmission after error: newAvatar is empty, avatarId still has value
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
use Coroq\Form\FormItem\EmailInput;
class AddressForm extends Form {
public readonly TextInput $street;
public readonly TextInput $city;
public readonly TextInput $postal;
public function __construct() {
$this->street = new TextInput();
$this->city = new TextInput();
$this->postal = new TextInput();
}
}
class UserForm extends Form {
public readonly TextInput $name;
public readonly EmailInput $email;
public readonly AddressForm $address;
public function __construct() {
$this->name = new TextInput();
$this->email = new EmailInput();
$this->address = new AddressForm();
}
}
$form = new UserForm();
$form->setValue([
'name' => 'Taro Yamada',
'email' => '[email protected]',
'address' => [
'street' => '1-1-1 Shibuya',
'city' => 'Tokyo',
'postal' => '150-0001'
]
]);
// Full IDE support for nested access
echo $form->address->street->getValue();
echo $form->address->postal->getValue();
// Hierarchical values
$values = $form->getValue();
/*
[
'name' => 'Taro Yamada',
'email' => '[email protected]',
'address' => [
'street' => '1-1-1 Shibuya',
'city' => 'Tokyo',
'postal' => '150-0001'
]
]
*/
// Alternative: getItem() method
$addressForm = $form->getItem('address'); // Returns FormInterface
if ($addressForm instanceof FormInterface) {
$street = $addressForm->getItem('street');
echo $street->getValue();
}
use Coroq\Form\Form;
use Coroq\Form\RepeatingForm;
use Coroq\Form\FormItem\EmailInput;
class ContactForm extends Form {
public readonly RepeatingForm $emails;
public function __construct() {
$this->emails = (new RepeatingForm())->setFactory(function(int $index) {
$email = new EmailInput();
$email->setRequired($index === 0);
$email->setLabel($index === 0 ? 'Primary Email' : 'Additional Email');
return $email;
});
$this->emails->setMinItemCount(3);
$this->emails->setMaxItemCount(5);
}
}
$form = new ContactForm();
$form->setValue(['emails' => ['[email protected]', '[email protected]']]);
if ($form->validate()) {
// Access items by index
echo $form->emails->getItem(0)->getValue(); // '[email protected]'
echo $form->emails->getItem(1)->getValue(); // '[email protected]'
echo $form->emails->getItem(2)->getValue(); // '' (minItemCount=3)
// Get all values
print_r($form->emails->getValue());
// ['[email protected]', '[email protected]', '']
// Get only filled values
print_r($form->emails->getFilledValue());
// [0 => '[email protected]', 1 => '[email protected]']
}
use Coroq\Form\RepeatingForm;
use Coroq\Form\FormItem\TelInput;
// Complex business logic
$phoneNumbers = (new RepeatingForm())->setFactory(function(int $index) {
$phone = new TelInput();
if ($index === 0) {
$phone->setLabel('Primary Phone')->setRequired(true);
} elseif ($index === 1) {
$phone->setLabel('Mobile Phone')->setRequired(false);
} else {
$phone->setLabel('Emergency Contact #' . ($index - 1))->setRequired(false);
}
return $phone;
});
$phoneNumbers->setMinItemCount(2); // Always show primary + mobile
$phoneNumbers->setMaxItemCount(10); // Max 10 total
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
class AddressForm extends Form {
public readonly TextInput $street;
public readonly TextInput $city;
public readonly TextInput $postal;
public function __construct() {
$this->street = new TextInput();
$this->city = new TextInput();
$this->postal = new TextInput();
}
}
class UserForm extends Form {
public readonly RepeatingForm $addresses;
public function __construct() {
// RepeatingForm of nested Forms
$this->addresses = (new RepeatingForm())->setFactory(function(int $index) {
$form = new AddressForm();
// First address
use Coroq\Form\RepeatingForm;
use Coroq\Form\FormItem\EmailInput;
$emails = (new RepeatingForm())->setFactory(fn($i) => new EmailInput());
$emails->addItem('[email protected]');
$emails->addItem('[email protected]');
echo $emails->count(); // 2
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
use Coroq\Form\FormItem\Derived;
class UserForm extends Form {
public readonly TextInput $firstName;
public readonly TextInput $lastName;
public readonly Derived $fullName;
public function __construct() {
$this->firstName = new TextInput();
$this->lastName = new TextInput();
// Derived field calculates value from sources
$this->fullName = (new Derived())
->setValueCalculator(fn($first, $last) => $first . ' ' . $last)
->addSource($this->firstName)
->addSource($this->lastName);
}
}
$form = new UserForm();
$form->setValue([
'firstName' => 'Taro',
'lastName' => 'Yamada'
]);
echo $form->fullName->getValue(); // "Taro Yamada"
// If a source is invalid, getValue() returns null
$form->firstName->setValue(''); // Empty (fails validation if
use Coroq\Form\Form;
use Coroq\Form\FormItem\NumberInput;
use Coroq\Form\FormItem\IntegerInput;
use Coroq\Form\FormItem\Derived;
class OrderForm extends Form {
public readonly NumberInput $price;
public readonly IntegerInput $quantity;
public readonly Derived $total;
public function __construct() {
$this->price = new NumberInput();
$this->quantity = new IntegerInput();
// Calculate total price
$this->total = (new Derived())
->setValueCalculator(fn($price, $quantity) => $price * $quantity)
->addSource($this->price)
->addSource($this->quantity);
}
}
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
use Coroq\Form\FormItem\Derived;
use Coroq\Form\Error\InvalidError;
class RegistrationForm extends Form {
public readonly TextInput $password;
public readonly TextInput $passwordConfirm;
public readonly Derived $passwordMatch;
public function __construct() {
$this->password = (new TextInput())
->setMinLength(8);
$this->passwordConfirm = new TextInput();
// Validate that passwords match (no value calculator needed)
$this->passwordMatch = (new Derived())
->setValidator(function($password, $confirm, $calculated) {
// $password = source 1 value
// $confirm = source 2 value
// $calculated = null (no setValueCalculator)
return $password !== $confirm
? new InvalidError($this)
: null;
})
->addSource($this->password)
->addSource($this->passwordConfirm);
}
}
$form = new RegistrationForm();
$form->setValue([
'password' => 'secret123',
'passwordConfirm' => 'secret456'
]);
if (!$form->validate()) {
if ($form->passwordMatch->hasError()) {
echo "Passwords must match";
}
}
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
use Coroq\Form\FormItem\Derived;
use Coroq\Form\Error\TooLongError;
class ProfileForm extends Form {
public readonly TextInput $firstName;
public readonly TextInput $lastName;
public readonly Derived $displayName;
public function __construct() {
$this->firstName = new TextInput();
$this->lastName = new TextInput();
// Calculate display name and validate its length
$this->displayName = (new Derived())
->setValueCalculator(fn($first, $last) => strtoupper($first . ' ' . $last))
->setValidator(function($first, $last, $calculated) {
// $first = source 1 value
// $last = source 2 value
// $calculated = the computed value from setValueCalculator
return strlen($calculated) > 50
? new TooLongError($this)
: null;
})
->addSource($this->firstName)
->addSource($this->lastName);
}
}
$form = new ProfileForm();
$form->setValue(['firstName' => 'Taro', 'lastName' => 'Yamada']);
echo $form->displayName->getValue(); // "TARO YAMADA" (calculated)
// Validation runs on the calculated value
$form->setValue(['firstName' => str_repeat('A', 30), 'lastName' => str_repeat('B', 30)]);
$form->validate(); // Fails - displayName has TooLongError
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
use Coroq\Form\FormItem\EmailInput;
use Coroq\Form\FormItem\IntegerInput;
use Coroq\Form\FormItem\Select;
use Coroq\Form\ErrorMessageFormatter;
use Coroq\Form\Error\EmptyError;
use Coroq\Form\Error\InvalidError;
use Coroq\Form\Error\TooSmallError;
class UserRegistrationForm extends Form {
public readonly TextInput $name;
public readonly EmailInput $email;
public readonly IntegerInput $age;
public readonly Select $country;
public function __construct() {
$this->name = (new TextInput())
->setLabel('Name')
->setMaxLength(100);
$this->email = (new EmailInput())
->setLabel('Email');
$this->age = (new IntegerInput())
->setLabel('Age')
->setRequired(false) // Make optional
->setMin(18)
->setMax(120);
$this->country = (new Select())
->setLabel('Country')
->setOptions([
'us' => 'United States',
'jp' => 'Japan',
'uk' => 'United Kingdom'
]);
}
}
// Setup error messages
$formatter = new ErrorMessageFormatter();
$formatter->setMessages([
EmptyError::class => 'This field is
// Recommended: Use Unicode Replacement Character for invalid UTF-8 bytes
mb_substitute_character(0xFFFD); // U+FFFD: �
mb_substitute_character('none'); // Remove invalid bytes silently
mb_substitute_character('long'); // Use U+XXXX notation
mb_substitute_character('entity'); // Use &#XXXX; HTML entities
use Coroq\Form\Form;
use Coroq\Form\FormItem\TextInput;
class MyForm extends Form {
public readonly TextInput $field;
// Define form items as typed readonly properties
}
$form = new MyForm();
// Values
$form->setValue(array $data);
$values = $form->getValue(); // All enabled items (raw values)
$parsed = $form->getParsedValue(); // All enabled items (parsed values)
$filled = $form->getFilledValue(); // Non-empty values only (raw)
$filledParsed = $form->getFilledParsedValue(); // Non-empty values (parsed)
// Validation
$valid = $form->validate();
$hasError = $form->hasError();
$errors = $form->getError(); // Array of errors
// Item access
$item = $form->getItem(mixed $name); // Get item by name
// State
$form->setRequired(bool);
$form->setReadOnly(bool);
$form->setDisabled(bool);
// Utility
$form->clear();
$isEmpty = $form->isEmpty();
use Coroq\Form\FormItem\TextInput;
$input = new TextInput();
// Values
$input->setValue(mixed $value);
$value = $input->getValue(); // Raw value (string)
$parsed = $input->getParsedValue(); // Parsed value (int, bool, DateTime, etc.) or null if empty/invalid
$input->clear();
// Validation
$valid = $input->validate();
$error = $input->getError(); // Error object or null
$hasError = $input->hasError();
// State
$input->setRequired(bool);
$input->setReadOnly(bool);
$input->setDisabled(bool);
$input->setLabel(string);
// Custom validation and error handling
$input->setValidator(?callable); // fn($formItem, $value): ?Error
$input->setErrorCustomizer(?\Closure); // fn($error, $formItem): Error
// Checks
$isEmpty = $input->isEmpty();
$isRequired = $input->isRequired();
$isReadOnly = $input->isReadOnly();
$isDisabled = $input->isDisabled();
use Coroq\Form\FormItem\Select;
use Coroq\Form\FormItem\MultiSelect;
$select = new Select();
$select->setOptions(array);
$label = $select->getSelectedLabel(); // string|null
$multi = new MultiSelect();
$multi->setOptions(array);
$multi->setMinCount(int);
$multi->setMaxCount(int);
$labels = $multi->getSelectedLabel(); // array
use Coroq\Form\FormItem\NumberInput;
use Coroq\Form\FormItem\IntegerInput;
$number = new NumberInput();
$number->setMin(string);
$number->setMax(string);
$value = $number->getNumber(); // float|null
$int = new IntegerInput();
$int->setMin(string);
$int->setMax(string);
$value = $int->getInteger(); // int|null
use Coroq\Form\FormItem\BooleanInput;
$bool = new BooleanInput();
$value = $bool->getBoolean(); // bool (true if not empty, false if empty)
// Note: Only '', null, and false are considered empty
use Coroq\Form\FormItem\FileInput;
$file = new FileInput();
$file->setMaxSize(int); // Max file size in bytes
$file->setMinSize(int); // Min file size in bytes
$file->setAllowedMimeTypes(array); // e.g., ['image/jpeg', 'image/png']
$file->setAllowedExtensions(array); // e.g., ['jpg', 'png', 'pdf']
$path = $file->getValue(); // string|null - file path
// Note: Usually setRequired(false) - file might already be uploaded
use Coroq\Form\RepeatingForm;
use Coroq\Form\FormItem\EmailInput;
// Create with factory
$repeating = (new RepeatingForm())->setFactory(function(int $index) {
return (new EmailInput())->setRequired($index === 0);
});
// Structural constraints
$repeating->setMinItemCount(int); // Always have at least N items
$repeating->setMaxItemCount(int); // Never exceed N items
$min = $repeating->getMinItemCount();
$max = $repeating->getMaxItemCount();
// Values
$repeating->setValue(array); // Recreates all items from factory
$values = $repeating->getValue(); // Array of values (int-indexed)
$parsed = $repeating->getParsedValue(); // Array of parsed values
$filled = $repeating->getFilledValue(); // Non-empty values only
$filledParsed = $repeating->getFilledParsedValue();
// Item access
$item = $repeating->getItem(int); // Get item at index (or null)
$items = $repeating->getItems(); // Get all items
$count = $repeating->count(); // Number of items
// Manual item addition
$item = $repeating->addItem(?string); // Add new item, returns the item
// Validation
$valid = $repeating->validate(); // Validates each item
$errors = $repeating->getError(); // Array of errors (int-indexed)
$hasError = $repeating->hasError();
// State (same as Form/Input)
$repeating->setRequired(bool);
$repeating->setReadOnly(bool);
$repeating->setDisabled(bool);
$repeating->clear(); // Clears all item values
$isEmpty = $repeating->isEmpty();
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.