1. Go to this page and download the library: Download vincent4vx/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/ */
vincent4vx / form example snippets
use Quatrevieux\Form\ContainerRegistry;
use Quatrevieux\Form\DefaultFormFactory;
use Quatrevieux\Form\Util\Functions;
// Initialize the container registry with your PSR-11 container.
$registry = new ContainerRegistry($container);
// Instantiate the runtime form factory (for development)
$factory = DefaultFormFactory::runtime($registry);
// Instantiate the generated form factory (for production)
$factory = DefaultFormFactory::generated(
registry: $registry,
savePathResolver: Functions::savePathResolver(self::GENERATED_DIR),
);
use Quatrevieux\Form\Validator\Constraint\Length;
use Quatrevieux\Form\Validator\Constraint\Regex;
use Quatrevieux\Form\Validator\Constraint\PasswordStrength;
use Quatrevieux\Form\Validator\Constraint\EqualsWith;
use Quatrevieux\Form\Validator\Constraint\ValidateVar;
use Quatrevieux\Form\Component\Csrf\Csrf;
class RegistrationRequest
{
// Non-nullable field will be considered as ing $email;
// Optional fields can be defined using the "?" operator, making them nullable
#[Length(min: 3, max: 256)]
public ?string $name;
// You can use the Csrf component to add a CSRF token to your form (
$form = $factory->create(RegistrationRequest::class);
// submit() will validate the data and return a SubmittedForm object
// The value must be stored in a variable, because the $form object is immutable
$submitted = $form->submit($_POST);
if ($submitted->valid()) {
// If the submitted data is valid, you can access the form data, which is an instance of the RegistrationRequest class:
$value = $submitted->value();
$value->username;
$value->password;
// ...
} else {
// If the submitted data is invalid, you can access the errors like this:
$errors = $submitted->errors();
/** @var \Quatrevieux\Form\Validator\FieldError $error */
foreach ($errors as $field => $error) {
$error->localizedMessage(); // The error message, translated using the translator
$error->code; // UUID of the constraint that failed
$error->parameters; // Failed constraint parameters. For example, the "min" and "max" parameters for the Length constraint
}
}
use Quatrevieux\Form\Embedded\ArrayOf;
use Quatrevieux\Form\Embedded\Embedded;
use Quatrevieux\Form\Validator\Constraint\Length;
use Quatrevieux\Form\Validator\Constraint\PasswordStrength;
class User
{
#[Length(min: 3, max: 12)]
public string $pseudo;
// Use the Embedded attribute component to embed a form into another form
// Note: you can mark the property as nullable to make it optional
#[Embedded(Credentials::class)]
public Credentials $credentials;
// Works in the same way for arrays by using the ArrayOf attribute
#[ArrayOf(Address::class)]
public array $addresses;
}
class Credentials
{
#[Length(min: 3, max: 256)]
public string $username;
#[PasswordStrength]
public string $password;
}
class Address
{
public string $street;
public string $city;
public string $zipCode;
public string $country;
}
use Quatrevieux\Form\Validator\Constraint\ValidationMethod;
class MyForm
{
#[ValidationMethod('validateFoo')]
public string $foo;
public function validateFoo(string $value): ?string
{
// Do your validation here
if (...) {
// Return an error message if the value is invalid (it will be translated using the translator)
// Note: the return value may also be a boolean (use message defined in the constraint) or a FieldError object
// See the documentation for more information
return 'Foo is invalid';
}
return null;
}
}
use Quatrevieux\Form\Validator\Constraint\ConstraintValidatorInterface;
use Quatrevieux\Form\Validator\Constraint\ConstraintInterface;
use Quatrevieux\Form\Validator\Constraint\ValidateBy;
class MyForm
{
#[ValidateBy(MyValidator::class)]
public string $foo;
}
// Declare your validator class
class MyValidator implements ConstraintValidatorInterface
{
public function __construct(
// Inject your dependencies here
private readonly MyFooService $service,
) {
}
public function validate(ConstraintInterface $constraint, mixed $value, object $data): ?FieldError
{
// $constraint is the ValidateBy constraint, which can be used to access the parameters
// $value is the value of the field
// $data is the form object (MyForm in this case)
if (!$this->service->isValid($value)) {
// No sugar here, you have to create the FieldError object yourself
// Note: it will be automatically translated using the translator
return new FieldError('Foo is invalid');
}
return null;
}
}
use Quatrevieux\Form\Validator\Constraint\ConstraintInterface;
use Quatrevieux\Form\Validator\Constraint\SelfValidatedConstraint;
use Quatrevieux\Form\Validator\FieldError;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class MyConstraint extends SelfValidatedConstraint
{
// It's recommended to declare an unique code for your constraint
public const CODE = 'c6241cc4-c7f5-4951-96b5-bf3e69f9ed15';
public function __construct(
// Declare your parameters here
public readonly string $foo,
// It's recommended to declare a message parameter, which will be used as the default error message
// Placeholders can be used to display parameters values
public readonly string $message = 'Foo is invalid : {{ foo }}',
) {
}
public function validate(ConstraintInterface $constraint, mixed $value, object $data): ?FieldError
{
// $constraint is same as $this
// $value is the value of the field
// $data is the form object (MyForm in this case)
if (...) {
return new FieldError($constraint->message, ['foo' => $constraint->foo], self::CODE);
}
return null;
}
}
use Quatrevieux\Form\Validator\Constraint\ConstraintInterface;
use Quatrevieux\Form\Validator\Constraint\ConstraintValidatorInterface;
use Quatrevieux\Form\Validator\FieldError;
use Quatrevieux\Form\RegistryInterface;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class MyConstraint implements ConstraintInterface
{
// It's recommended to declare an unique code for your constraint
public const CODE = 'c6241cc4-c7f5-4951-96b5-bf3e69f9ed15';
public function __construct(
// Declare your parameters here
public readonly string $foo,
// It's recommended to declare a message parameter, which will be used as the default error message
// Placeholders can be used to display parameters values
public readonly string $message = 'Foo is invalid : {{ foo }}',
) {
}
public function getValidator(RegistryInterface $registry): ConstraintValidatorInterface
{
// Resolve the validator from the registry
return $registry->getConstraintValidator(MyConstraintValidator::class);
}
}
class MyConstraintValidator implements ConstraintValidatorInterface
{
public function __construct(
// Inject your dependencies here
private readonly MyFooService $service,
) {
}
public function validate(ConstraintInterface $constraint, mixed $value, object $data): ?FieldError
{
// $constraint is the MyConstraint constraint, which can be used to access the parameters
// $value is the value of the field
// $data is the form object (MyForm in this case)
if (!$this->service->isValid($value, $constraint->foo)) {
return new FieldError($constraint->message, ['foo' => $constraint->foo], MyConstraint::CODE);
}
return null;
}
}
#[MyConstraint('bar')]
public ?string $foo;
if (($error = ($fooConstraint = new MyConstraint('bar'))->getValidator($this->registry)->validate($fooConstraint, $data->foo ?? null, $data)) !== null) {
$errors['foo'] = $error;
}
use Quatrevieux\Form\Transformer\Field\FieldTransformerInterface;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class MyTransformer implements FieldTransformerInterface
{
public function __construct(
// Declare your parameters here
private readonly string $foo,
) {
}
public function transformFromHttp(mixed $value): mixed
{
// $value is the HTTP value
// The value will be null if the field is not present in the HTTP request
// Transform the value here
return $value;
}
public function transformToHttp(mixed $value): mixed
{
// $value is the form object value
// Transform the value here
return $value;
}
public function canThrowError(): bool
{
// Return true if the transformer can throw an error
// If true, the transformer will be wrapped in a try/catch block to mark the field as invalid
return false;
}
}
class MyForm
{
#[MyTransformer('bar')]
public ?string $foo;
}
use Quatrevieux\Form\Transformer\Field\DelegatedFieldTransformerInterface;
use Quatrevieux\Form\Transformer\Field\ConfigurableFieldTransformerInterface;
use Quatrevieux\Form\RegistryInterface;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class MyTransformer implements DelegatedFieldTransformerInterface
{
public function __construct(
// Declare your parameters here
public readonly string $foo,
) {
}
public function getTransformer(RegistryInterface $registry): ConfigurableFieldTransformerInterface
{
// Resolve the transformer from the registry
return $registry->getFieldTransformer(MyTransformerImpl::class);
}
}
class MyTransformerImpl implements ConfigurableFieldTransformerInterface
{
public function __construct(
// Declare your dependencies here
private readonly MyFooService $service,
) {
}
public function transformFromHttp(DelegatedFieldTransformerInterface $configuration, mixed $value): mixed
{
// $configuration is the MyTransformer attribute instance
// $value is the HTTP value
// The value will be null if the field is not present in the HTTP request
// Transform the value here
return $value;
}
public function transformToHttp(DelegatedFieldTransformerInterface $configuration, mixed $value): mixed
{
// $configuration is the MyTransformer attribute instance
// $value is the form object value
// Transform the value here
return $value;
}
}
class MyForm
{
#[ArrayShape([
'firstName' => 'string',
'lastName' => 'string',
// Use ? to mark the field as optional
'age?' => 'int',
// You can declare a sub array shape
'address' => [
'street' => 'string',
'city' => 'string',
'zipCode' => 'string|int', // You can use multiple types
],
])]
public array $person;
// You can define array as dynamic list
#[ArrayShape(key: 'int', value: 'int|float')]
public array $listOfNumbers;
// You can disable extra keys
#[ArrayShape(['foo' => 'string', 'bar' => 'int'], allowExtraKeys: false)]
public array $fixed;
}
class MyForm
{
#[Choice(['foo', 'bar'])]
public string $foo;
// Define labels for the choices
#[Choice([
'My first label' => 'foo',
'My other label' => 'bar',
])]
public string $bar;
}
class MyForm
{
#[EqualsWith('password', message: 'Passwords must be equals')]
public string $passwordConfirm;
public string $password;
}
class MyForm
{
#[EqualTo(10)]
public int $foo;
}
class MyForm
{
#[GreaterThan(10)]
public int $foo;
}
class MyForm
{
#[GreaterThanOrEqual(10)]
public int $foo;
}
class MyForm
{
#[IdenticalTo(10)]
public int $foo;
}
class MyForm
{
// Only check the minimum length
#[Length(min: 2)]
public string $foo;
// Only check the maximum length
#[Length(max: 32)]
public string $bar;
// For a fixed length
#[Length(min: 12, max: 12)]
public string $baz;
// Check the length is between 2 and 32 (
class MyForm
{
#[LessThan(10)]
public int $foo;
}
class MyForm
{
#[LessThanOrEqual(10)]
public int $foo;
}
class MyForm
{
#[NotEqualTo(10)]
public int $foo;
}
class MyForm
{
#[NotIdenticalTo(10)]
public int $foo;
}
class User
{
#[PasswordStrength(min:51, message:"Your password is too weak")]
private $password;
}
class MyForm
{
#[Regex('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')]
public ?string $uuid;
#[Regex('^[a-z]+$', flags: 'i')]
public string $foo;
}
class MyForm
{
// Explicitly mark the field as is m error message to override the default one
#[Required('This field is
class MyForm
{
// Constraint for simply check that the file is successfully uploaded
#[UploadedFile]
public UploadedFileInterface $file;
// You can also define a file size limit
#[UploadedFile(maxSize: 1024 * 1024)]
public UploadedFileInterface $withLimit;
// You can also define the file type using mime type filter, or file extension filter
// Note: this is not a security feature, you should always check the actual file type on the server side
#[UploadedFile(
allowedMimeTypes: ['image/*', 'application/pdf'], // wildcard is allowed for subtype
allowedExtensions: ['jpg', 'png', 'pdf'] // you can specify the file extension (without the dot)
)]
public UploadedFileInterface $withMimeTypes;
}
// Create and submit the form
// Here, $request is a PSR-7 ServerRequestInterface
$form = $factory->create(MyForm::class);
$submitted = $form->submit($request->getParsedBody() + $request->getUploadedFiles());
class MyRequest
{
#[ValidateArray(constraints: [
new Length(min: 3, max: 10),
new Regex(pattern: '/^[a-z]+$/'),
])]
public ?array $values;
}
class MyForm
{
#[ValidateBy(MyValidator::class, ['checksum' => 15])]
public string $foo;
}
class MyValidator implements ConstraintValidatorInterface
{
public function __construct(private ChecksumAlgorithmInterface $algo) {}
public function validate(ConstraintInterface $constraint, mixed $value, object $data): ?FieldError
{
if ($this->algo->checksum($value) !== $constraint->options['checksum']) {
return new FieldError('Invalid checksum');
}
return null;
}
}
class MyRequest
{
#[ValidateVar(ValidateVar::EMAIL)]
public ?string $email;
#[ValidateVar(ValidateVar::DOMAIN, options: FILTER_FLAG_HOSTNAME)] // You can add flags as an int
public ?string $domain;
#[ValidateVar(ValidateVar::INT, options: ['options' => ['min_range' => 0, 'max_range' => 100]])] // You can add options as an array
public ?float $int;
}
class MyForm
{
// Calling validateFoo() on this instance
#[ValidateMethod(method: 'validateFoo', parameters: [15], message: 'Invalid checksum')]
public string $foo;
// Calling Functions::validateFoo()
#[ValidateMethod(class: Functions::class, method: 'validateFoo', parameters: [15], message: 'Invalid checksum')]
public string $foo;
// Return a boolean, so the default error message is used
public function validateFoo(string $value, object $data, int $checksum)
{
return crc32($value) % 32 === $checksum;
}
// Return a string, so the string is used as error message
public function validateFoo(string $value, object $data, int $checksum)
{
if (crc32($value) % 32 !== $checksum) {
return 'Invalid checksum';
}
return null;
}
// Return a FieldError instance
public function validateFoo(string $value, object $data, int $checksum)
{
if (crc32($value) % 32 !== $checksum) {
return new FieldError('Invalid checksum');
}
return null;
}
}
class Functions
{
// You can also use a static method
public static function validateFoo(string $value, object $data, int $checksum): bool
{
return crc32($value) % 32 === $checksum;
}
}
class MyForm
{
#[ArrayCast(CastType::Int)]
public array $foo;
// Ignore original keys : the result will be a list of floats
#[ArrayCast(CastType::Float, preserveKeys: false)]
public array $bar;
}
class MyForm
{
#[Cast(CastType::Int)]
public $foo;
// By default, the cast is performed on the property type, so it's not needed here
public float $bar;
}
class MyForm
{
// Will transform "foo,bar,baz" to ["foo", "bar", "baz"]
#[Csv]
public array $foo;
// You can specify separator
#[Csv(separator: ';')]
public array $bar;
// You can use ArrayCast to cast values
#[Csv, ArrayCast(CastType::INT)]
public array $baz;
}
class MyForm
{
// Parse an HTML5 datetime-local input
#[DateTimeTransformer]
public ?DateTimeInterface $date;
// Use a custom format, class, and timezone
#[DateTimeTransformer(class: DateTime::class, format: 'd/m/Y', timezone: 'Europe/Paris')]
public ?DateTime $date;
}
class MyForm
{
public int $implicit = 42; // Implicitly define the default value. Will be applied after all other transformers.
#[DefaultValue(12.3)] // Explicitly define the default value. Default property value will be ignored.
public float $explicit = 0.0;
#[DefaultValue('foo,bar')] // When defined before other transformers, the value should be an HTTP value.
#[Csv]
public array $values;
}
class MyForm
{
// If MyEnum is a UnitEnum, the name will be used to get the enum instance
// Else, the value will be used
// If the value is not found, the field will be considered as invalid
#[Enum(MyEnum::class)]
public ?MyEnum $myEnum;
// Use the name instead of the value on BackedEnum
#[Enum(MyEnum::class, useName: true)]
public ?MyEnum $byName;
// If the value is not found, the field will be set to null without error
#[Enum(MyEnum::class, errorOnInvalid: false)]
public ?MyEnum $noError;
}
class MyRequest
{
#[Json] // By default, JSON objects will be returned as associative arrays.
public ?array $myArray;
#[Json(assoc: false, depth: 5)] // JSON objects will be returned as stdClass, and limit the depth to 5.
public ?object $myObject;
#[Json(encodeOptions: JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)] // Change the display of the JSON.
public mixed $pretty;
// Use ArrayShape to validate the JSON structure
#[Json, ArrayShape(['foo' => 'string', 'bar' => 'int'])]
public array $withShape;
}
class MyForm
{
#[TransformEach([
new Trim(),
new Csv(separator: ';'),
])]
public ?array $tags = null;
}
class MyForm
{
#[Trim]
public ?string $myString = null;
}
class MyForm
{
// You can customize the error message, and code, just like validation errors
#[TransformationError(message: 'This JSON is invalid', code: 'f59e2415-0b70-4177-9bc1-66ebbb65c75c'), Json]
public string $json;
// Fail silently: the field will be set to null, and no error will be displayed
#[TransformationError(ignore: true), Json]
public ?string $foo;
// Keep the original value instead of setting it to null
#[TransformationError(ignore: true, keepOriginalValue: true), Json]
public mixed $bar;
}
final class MyForm
{
#[Checkbox]
public bool $isAccepted;
// You can also define a custom http value
#[Checkbox(httpValue: 'yes')]
public bool $withCustomHttpValue;
// You can use validator to ensure the field is checked (or any other validation)
#[Checkbox, IsIdenticalTo(true, message: 'You must check this box')]
public bool $mustBeChecked;
}
class MyForm
{
// The CSRF token will be generated once per session
// Required constraint as no effect here : csrf token is always validated
#[Csrf]
public string $csrf;
// The CSRF token will be regenerated on each request
#[Csrf(refresh: true)]
public string $csrfRefresh;
}
// Add CsrfManager to the form registry (use PSR-11 container in this example)
$container->register(new CsrfManager($container->get(CsrfTokenManagerInterface::class)));
$registry = new ContainerRegistry($container);
$factory = DefaultFormFactory::create($registry);
// Create and submit the form
$form = $factory->create(MyForm::class);
$submitted = $form->submit($_POST);
$form = $factory->create(MyForm::class);
// The following code will not work:
$form->submit($_POST);
if (!$form->valid()) {
return $this->showErrors($form->errors());
}
return $this->process($form->value());
// The following code will work:
$submitted = $form->submit($_POST);
if (!$submitted->valid()) {
return $this->showErrors($submitted->errors());
}
return $this->process($submitted->value());