Updated June 2026. Tested on Symfony 7.x and PHP 8.4. Part of the Techalyst Symfony series.
Validation is how you make sure data meets your rules before it goes anywhere: a name is not empty, an email looks like an email, a price is positive. Symfony's Validator component lets you declare those rules right on your data as constraint attributes, then check them from anywhere. Because the rules live on the object, not on a form or a controller, they apply consistently however the data was created.
Declaring constraints
You attach #[Assert\...] attributes to the properties of an entity (or any object). Each one is a rule that property must satisfy:
use Symfony\Component\Validator\Constraints as Assert;
class Product
{
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 255)]
private string $name;
#[Assert\Positive]
private int $price;
#[Assert\Email]
private ?string $contactEmail = null;
}
This reads as exactly what it enforces: the name is required and between 3 and 255 characters, the price must be positive, the email must be a valid address. There is a large library of built-in constraints, NotBlank, Length, Email, Range, Positive, Choice, Regex, Url, and more, so most rules are a matter of picking the right one.
How forms trigger it
If you use Symfony forms, validation is automatic. When you call $form->isValid(), the validator runs every constraint on the bound entity, and any failures become field errors that render next to the inputs. You write the rules once on the entity, and the form surfaces them without extra wiring.
Validating manually
Outside a form, an API endpoint, a console command, you run the validator yourself by injecting ValidatorInterface:
public function save(Product $product, ValidatorInterface $validator): Response
{
$errors = $validator->validate($product);
if (count($errors) > 0) {
return $this->json(['errors' => (string) $errors], 422);
}
// ...persist
}
validate returns a list of violations, empty if everything passed. You decide what to do with them, return a 422 for an API, add flash messages, whatever fits.
Custom messages
Every constraint takes a message so the error reads the way you want:
#[Assert\NotBlank(message: 'Please enter a product name.')]
private string $name;
Two things worth knowing
When you need different rules in different situations, say a password is required on registration but optional on profile edit, validation groups let you tag constraints and validate only a chosen group. And for database-level uniqueness, like a unique email, there is a class-level #[UniqueEntity] constraint that checks the table, something property constraints cannot do on their own.
Wrapping up
Symfony validation lives on your data as constraint attributes: declare rules like NotBlank, Length, and Email on entity properties, and they enforce themselves wherever the object is validated. Forms run them automatically through isValid, and elsewhere you inject the ValidatorInterface and call validate to get a list of violations. Customise the message, reach for validation groups when rules differ by scenario, and use UniqueEntity for database uniqueness. Keeping the rules on the object means they hold no matter how the data arrives.
All comments ()
No comments yet
Be the first to leave a comment on this post.