Validation

On the Forms page you saw that submitting a form calls $form->isValid() before the data gets used, but that method doesn't invent its rules out of nowhere. Symfony's Validator component reads a set of constraints you declare directly on your Entity's properties, using plain PHP 8 attributes. This page shows how those constraints are written, how the Form component uses them automatically, and how to run the same checks manually outside of a form.

Constraints as PHP Attributes

Instead of a separate validation configuration file, constraints live as attributes right on the property they protect — the rule and the data it governs sit in the same place. The Assert namespace ships a large set of ready-made constraints; three of the most common are shown below.

<?php
use Symfony\Component\Validator\Constraints as Assert;

#[Assert\NotBlank]
private string $title;

#[Assert\Length(min: 5, max: 255)]
private string $title;

#[Assert\Email]
private string $authorEmail;
  • NotBlank — rejects empty strings, empty arrays, and null.
  • Length — enforces a minimum and/or maximum character count.
  • Email — checks the value looks like a valid email address.

Worked Example: The Article Entity

Combining several constraints on one Entity gives a complete picture. Here, Article requires a title of a sensible length, a non-empty body, and a valid author email address — all declared next to the property they protect.

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity]
class Article
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private int $id;

    #[ORM\Column(length: 255)]
    #[Assert\NotBlank]
    #[Assert\Length(min: 5, max: 255)]
    private string $title;

    #[ORM\Column]
    #[Assert\NotBlank]
    private string $body;

    #[ORM\Column]
    #[Assert\NotBlank]
    #[Assert\Email]
    private string $authorEmail;

    // getters and setters omitted for brevity
}
Note: This is correct, real Symfony code meant to be copied into your own project — this website doesn't have Symfony installed, so nothing here runs live on this page.

How This Ties Into the Form Component

When a form is bound to an Article and submitted, Symfony maps the request data onto the Entity's properties and then automatically runs the Validator against every constraint attribute it finds — this is exactly what happens under the hood of $form->isValid() from the Forms page.

if ($form->isSubmitted() && $form->isValid()) {
    // $article already satisfies every Assert constraint on its properties
    $entityManager->persist($article);
    $entityManager->flush();
}

Validating an Object Manually

You aren't limited to validating inside a form submission. Any service or controller can ask the container for the ValidatorInterface and call validate() directly on an object — handy for checking data that arrived from an API request, a console command, or anywhere else outside the Form component.

<?php

use Symfony\Component\Validator\Validator\ValidatorInterface;

class ArticleController extends AbstractController
{
    public function preview(ValidatorInterface $validator)
    {
        $article = new Article();
        $article->setTitle('Hi');

        $errors = $validator->validate($article);

        if (count($errors) > 0) {
            return $this->render('article/errors.html.twig', ['errors' => $errors]);
        }

        return $this->render('article/valid.html.twig');
    }
}

Here $title is only two characters, so validate() returns a ConstraintViolationList containing one violation for the Length constraint — no form submission required to trigger the check.

Displaying Errors in a Twig Form

When validation runs as part of a form submission, each field's own errors can be rendered right next to it with the form_errors() Twig function, keeping the message close to the input the reader needs to fix.

{{ form_start(form) }}
    {{ form_row(form.title) }}
    {{ form_errors(form.title) }}

    {{ form_row(form.body) }}
    {{ form_errors(form.body) }}
{{ form_end(form) }}
Next up: Head to Services & Dependency Injection to see how classes like controllers get the objects they depend on — such as this ValidatorInterface — supplied to them automatically.