Documentation menu
On this page
Components · v1.x
Validator
The Validator component checks objects, values and arrays against constraints. A constraint is a class used either as a PHP attribute or as an object; its validator is built by the container, so custom validators can use services.
Quick start
<?php
declare(strict_types=1);
namespace App\Dto;
use NeoPHP\Component\Validator\Constraint as Assert;
class SignupDto
{
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 20)]
public ?string $username = null;
#[Assert\NotBlank]
#[Assert\Email]
public ?string $email = null;
#[Assert\Length(min: 8)]
public ?string $password = null;
#[Assert\EqualTo(propertyPath: 'password', message: 'The passwords do not match.')]
public ?string $confirm = null;
#[Assert\Valid]
public ?AddressDto $address = null;
}
In a controller, validate() returns the violations:
$violations = $this->validate($dto);
if (count($violations) > 0) {
return $this->json(['errors' => $violations->toArray()], 422);
}
Outside a controller, inject NeoPHP\Component\Validator\Contract\ValidatorInterface.
Validating
| Call | Validates |
|---|---|
validate($object) |
the constraints declared with attributes on the object (properties and class) |
validate($value, new Email()) |
a value with one constraint |
validate($value, [new NotBlank(), new Length(min: 3)]) |
a value with several constraints |
validate($data, ['email' => [new NotBlank(), new Email()], 'age' => new Range(min: 18)]) |
an array (or object) field by field; a missing field is null |
validateProperty($object, 'email') |
one property of an object |
validateOrFail(...) |
same as validate(), throws a ValidationFailedException (HTTP 422) when it fails |
<?php
declare(strict_types=1);
namespace App\Service;
use NeoPHP\Component\Validator\Constraint\Email;
use NeoPHP\Component\Validator\Constraint\NotBlank;
use NeoPHP\Component\Validator\Contract\ValidatorInterface;
final class NewsletterSubscriber
{
public function __construct(private ValidatorInterface $validator)
{
}
public function subscribe(string $email): void
{
$this->validator->validateOrFail($email, [new NotBlank(), new Email()]);
}
}
Violations
validate() returns a ViolationList (countable and iterable over Violation objects):
| Method | Returns |
|---|---|
count($violations) / count() |
number of violations |
has(?string $path = null) |
whether there is a violation (for a path) |
first(?string $path = null) |
first message, or null |
messages(string $path) |
messages of a path |
get(string $path) |
Violation objects of a path |
all() |
all the Violation objects |
toArray() |
['email' => ['This value is not a valid email address.'], ...] |
add(Violation $violation) / addAll(ViolationList $list) |
adds violations |
Violation: getMessage(), getMessageTemplate(), getParameters(), getPropertyPath(), getInvalidValue(), getConstraint(); it can be cast to string.
Paths: email, address.city (with Valid), tags[1] (with All), data[email] (with Collection).
Constraints
All the constraints are in NeoPHP\Component\Validator\Constraint.
| Constraint | Options |
|---|---|
NotBlank |
allowNull, trim (blank: null, '', [], false) |
Blank, NotNull, IsNull, IsTrue, IsFalse |
|
Type |
type (string, int, float, bool, array, numeric, scalar, iterable, callable, object, alpha, digit, alnum or a class; several allowed) |
Length |
exactly, min, max (characters, UTF-8) |
Count |
exactly, min, max (elements of an array or Countable) |
Range |
min, max (numbers or dates) |
EqualTo, NotEqualTo, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual |
value or propertyPath (compares with another property) |
Positive, PositiveOrZero, Negative, NegativeOrZero |
|
Email, Uuid, Json, Date (Y-m-d) |
|
Url |
protocols (default ['http', 'https']) |
Ip |
version (4, 6 or all) |
DateTime |
format (default Y-m-d H:i:s) |
Regex |
pattern, match (false: the value must not match) |
Choice |
choices or callback, multiple, min, max, strict (default true) |
All |
constraints applied to each element |
Collection |
fields (['email' => [...]]), allowExtraFields, allowMissingFields |
Valid |
validates the nested object (or each object of an array); traverse |
Callback |
callback: method of the object or callable (class or property) |
File |
maxSize (500k, 2M, 1Gi or bytes), mimeTypes (['application/pdf', 'image/*']), extensions (['pdf']); validates an UploadedFile, a SplFileInfo or a path |
Image |
the File options (mimeTypes defaults to image/*), minWidth, maxWidth, minHeight, maxHeight |
Every constraint accepts groups. Except NotBlank, NotNull and IsNull, constraints accept null and '': add NotBlank to make a value required.
File::parseSize(int|string $size): int converts a size (2M, 1Gi) into bytes.
Messages
Every constraint accepts message, or its specific messages (minMessage, maxMessage, exactMessage, typeMessage, notInRangeMessage, multipleMessage, missingFieldsMessage, extraFieldsMessage, maxSizeMessage, mimeTypesMessage...).
#[Assert\Length(min: 3, minMessage: 'At least {{ limit }} characters.')]
public ?string $username = null;
Placeholders: {{ value }}, {{ limit }}, {{ min }}, {{ max }}, {{ compared_value }}, {{ choices }}, {{ type }}, and for files {{ size }}, {{ types }}, {{ extension }}, {{ width }}, {{ height }}.
The messages are translated in the current locale through the validators domain of the Translation package (the key is the English message); an untranslated message stays in English. The framework ships no translation: the application translates or rewords the messages it needs in translations/validators.{locale}.yaml (or .xlf):
"This value should not be blank.": "Cette valeur ne doit pas être vide."
"This value is too long. It should have {{ limit }} character(s) or less.": "Cette chaîne est trop longue. Elle doit avoir au maximum {{ limit }} caractère(s)."
The keys are the default messages of the constraints, or the message option given to a constraint.
Groups
A constraint belongs to the Default group (AbstractConstraint::DEFAULT_GROUP), unless groups is given. validate() validates the Default group, unless groups are given:
#[Assert\NotBlank(groups: ['create'])]
public ?string $password = null;
$this->validate($dto, null, ['Default', 'create']);
Nested data
<?php
declare(strict_types=1);
namespace App\Dto;
use NeoPHP\Component\Validator\Constraint as Assert;
class OrderDto
{
#[Assert\Valid]
public array $lines = [];
#[Assert\All([new Assert\NotBlank(), new Assert\Length(max: 20)])]
public array $tags = [];
#[Assert\Collection(fields: [
'email' => [new Assert\NotBlank(), new Assert\Email()],
'phone' => new Assert\Regex('/^\+?[0-9 ]+$/'),
], allowMissingFields: true)]
public array $contact = [];
}
Callback
<?php
declare(strict_types=1);
namespace App\Dto;
use DateTimeImmutable;
use NeoPHP\Component\Validator\Constraint as Assert;
use NeoPHP\Component\Validator\Context\ExecutionContext;
#[Assert\Callback('validatePeriod')]
class BookingDto
{
public ?DateTimeImmutable $start = null;
public ?DateTimeImmutable $end = null;
public function validatePeriod(ExecutionContext $context): void
{
if ($this->start !== null && $this->end !== null && $this->end < $this->start) {
$context->addViolation('The end must be after the start.', [], 'end');
}
}
}
On a property, the method receives ($value, ExecutionContext $context). new Callback(fn (mixed $value, ExecutionContext $context): mixed => ...) works with a closure.
Custom constraints
A constraint extends AbstractConstraint. Its validator is the class with the same name followed by Validator (override validatedBy() to change it). The validator is built by the container: its dependencies are autowired.
<?php
declare(strict_types=1);
namespace App\Validator;
use Attribute;
use NeoPHP\Component\Validator\Contract\AbstractConstraint;
#[Attribute(Attribute::TARGET_PROPERTY)]
class UniqueUsername extends AbstractConstraint
{
public string $message = 'The username {{ value }} is already used.';
}
<?php
declare(strict_types=1);
namespace App\Validator;
use App\Repository\UserRepository;
use NeoPHP\Component\Validator\Context\ExecutionContext;
use NeoPHP\Component\Validator\Contract\AbstractConstraintValidator;
use NeoPHP\Component\Validator\Contract\ConstraintInterface;
class UniqueUsernameValidator extends AbstractConstraintValidator
{
public function __construct(protected UserRepository $users)
{
}
public function validate(mixed $value, ConstraintInterface $constraint, ExecutionContext $context): void
{
$this->expect($constraint, UniqueUsername::class);
if ($this->isEmpty($value)) {
return;
}
if ($this->users->existsByUsername((string) $value)) {
$context->addViolation($constraint->message, ['value' => static::formatValue($value)]);
}
}
}
AbstractConstraintValidator helpers:
| Method | Description |
|---|---|
isEmpty(mixed $value): bool |
true for null and '' |
toString(mixed $value): ?string |
string of a scalar or Stringable, else null |
expect(ConstraintInterface $constraint, string $class): void |
throws a ValidatorException when the constraint has another class |
static formatValue(mixed $value): string |
value formatted for a message ("text", null, true, dates...) |
Execution context
NeoPHP\Component\Validator\Context\ExecutionContext is given to validators and callbacks:
| Method | Description |
|---|---|
addViolation(string $message, array $parameters = [], ?string $path = null, mixed $invalidValue = null) |
adds a violation; $path is relative to the current path |
validate(mixed $value, ConstraintInterface|array $constraints, string $path = '') |
validates a nested value |
validateObject(object $object, string $path = '') |
validates a nested object with its attributes |
getViolations() |
violations collected so far |
getPropertyPath(), getValue(), getConstraint() |
current path, value and constraint |
getObject() |
object being validated |
getRoot() |
value given to validate() |
getGroups() |
validated groups |
JSON errors
ValidationFailedException has the HTTP status 422. For a request that expects JSON, an uncaught one returns a 422 response with the violations:
{"error": {"status": 422, "message": "The data is not valid: 1 violation(s).", "violations": {"username": ["This value should not be blank."]}}}
API
ValidatorInterface
NeoPHP\Component\Validator\Contract\ValidatorInterface, implemented by ValidatorManager (extends AbstractValidator):
| Method | Description |
|---|---|
validate(mixed $value, ConstraintInterface|array|null $constraints = null, array $groups = ['Default']): ViolationList |
validates a value, an object or an array |
validateProperty(object $object, string $property, array $groups = ['Default']): ViolationList |
validates one property |
validateOrFail(mixed $value, ConstraintInterface|array|null $constraints = null, array $groups = ['Default']): void |
throws a ValidationFailedException |
Contracts
| Interface / class | Methods |
|---|---|
ConstraintInterface |
validatedBy(): string, getGroups(): array, inGroups(array $groups): bool |
AbstractConstraint |
implements ConstraintInterface, public $groups, protected setGroups(?array $groups) |
ConstraintValidatorInterface |
validate(mixed $value, ConstraintInterface $constraint, ExecutionContext $context): void |
AbstractConstraintValidator |
base class of the validators (see Custom constraints) |
Controllers
validate(mixed $value, ConstraintInterface|array|null $constraints = null, array $groups = ['Default']): ViolationList is available in controllers (see the Controller documentation).
Exceptions
| Exception | Description |
|---|---|
ValidatorException |
invalid constraint or validator |
ValidationFailedException |
thrown by validateOrFail(); create(ViolationList $violations), getViolations(): ViolationList; status 422 |
Changelog
- v1.20.0 — Messages translated through the Translation package (domain validators).
- v1.12.0 —
FileandImageconstraints. - v1.10.0 — Constraints usable as attributes or objects, validation of objects, values and arrays, groups,
Valid,All,Collection,Callback, custom constraints with autowired validators,validate()in controllers, 422 JSON response forValidationFailedException.