Validation
Validation rules in Symfony are written as attributes right above the entity's properties. A form applies them by itself: isValid() returns false, and error messages appear next to the fields.
Constraints
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Post
{
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Assert\Length(min: 5, max: 255)]
private ?string $title = null;
#[ORM\Column(type: Types::TEXT)]
#[Assert\NotBlank]
private ?string $body = null;
}Nothing else is needed: the form from the previous lesson starts rejecting an empty or too short title.
Common constraints
| Constraint | What it checks |
|---|---|
NotBlank | The value is not empty |
Length(min: 3, max: 50) | String length |
Email | An email address |
Url | A site address |
Positive, PositiveOrZero | The sign of a number |
Range(min: 1, max: 100) | A number within bounds |
Choice(['draft', 'published']) | One of the values |
Regex('/^[a-z0-9-]+$/') | A pattern match |
Image(maxSize: '2M') | The file is an image of at most 2 MB |
Custom messages
#[Assert\NotBlank(message: 'Enter a title')]
#[Assert\Length(
min: 5,
minMessage: 'The title must be at least {{ limit }} characters long',
)]
private ?string $title = null;Uniqueness
A check against the database is a constraint above the class:
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
#[ORM\Entity]
#[UniqueEntity('email', message: 'This address is already taken')]
class User
{
#[ORM\Column(unique: true)]
#[Assert\Email]
private ?string $email = null;
}Validating by hand
Without a form, the ValidatorInterface service validates an object:
use Symfony\Component\Validator\Validator\ValidatorInterface;
public function import(ValidatorInterface $validator): Response
{
$post = new Post();
$post->setTitle('');
$errors = $validator->validate($post);
if (count($errors) > 0) {
foreach ($errors as $error) {
echo $error->getPropertyPath() . ': ' . $error->getMessage();
}
}
// …
}A rule that depends on several fields
use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[Assert\Callback]
public function validateDates(ExecutionContextInterface $context): void
{
if ($this->endsAt < $this->startsAt) {
$context->buildViolation('The end is before the start')
->atPath('endsAt')
->addViolation();
}
}Common mistake
Most constraints let an empty value through: #[Assert\Email] treats an empty string and null as valid. If a field is required, add #[Assert\NotBlank] next to it. And remember: the required attribute in the markup is checked only by the browser and is easy to bypass — the real check is always on the server.