Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Validation Constraints with @Assert

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

When you talk about validation, what you're really talking about is business rules validation. That's where you tell Symfony that the title is required and needs to be a certain length, or that some field should be a valid email address, or that the password must contain 2 upper case letters, 2 lower case letters, 3 unicode characters and at least 4 emojis. It's about making the data constrain to your application's rules.

Adding your First Assert Annotation

Symfony's validation is kinda interesting because you do not apply the validation rules to the form. Nope, you apply them to your class via annotations. Check this out: I want the title field to be required. To do that, type @NotBlank and hit tab to autocomplete to @Assert\NotBlank. Because I have the PHP annotations plugin installed, when I auto-completed that, it added a use statement on top that we need: use Symfony\Component\Validator\Constraints as Assert.

... lines 1 - 11
use Symfony\Component\Validator\Constraints as Assert;
... lines 13 - 16
class Article
... lines 19 - 27
... line 29
* @Assert\NotBlank()
private $title;
... lines 33 - 255

Without doing anything else, refresh the form - the title field is empty. Yes! That's our error!

This value should not be blank.

To customize that, add a message key to the annotation:

Get creative and think of a title!

... lines 1 - 27
... line 29
* @Assert\NotBlank(message="Get creative and think of a title!")
private $title;
... lines 33 - 257

Try it again - refresh and... nice!

The Built-in Validation Constraints

On the docs, click to go back to the documentation homepage. Then, under guides, find the "Validation" guide. Just like with the form fields, there are a bunch of built-in validation constraints that... can help you validate almost anything! And... just like with form field types, each validation constraint has different options that control its behavior.

For example - check out Length: you can set the min length with the min option, or max with max. Control their error messages with minMessage and maxMessage.

Oh, another way to see what the options are is to remember that every annotation has a concrete PHP class behind it. Thanks to the PHP annotations plugin, I can hold Command or Ctrl and click the annotation to jump to that class.

Nice! Every property becomes an option that you can pass to the annotation. We'll see this again later when we create our own custom validation constraint.

Anyways, we won't talk too much about validation constraints because... they're honestly pretty easy: it's usually a matter of finding which validation constraint you need and the options to pass to it.

The Callback Constraint

Oh, but there is one really cool constraint called Callback. This is the tool when you need to go rogue and do something totally custom. Check it out: create a method in your class and add @Assert\Callback() above it. Then, during validation, Symfony will call your method!

Let's copy this, find our Article class, go all the way to the bottom, and paste. Oh, I need to retype the end of ExecutionContextInterface and auto-complete it to get the use statement. Then, inside... it's awesome! We can do whatever we want!

... lines 1 - 12
use Symfony\Component\Validator\Context\ExecutionContextInterface;
... lines 14 - 17
class Article
... lines 20 - 257
* @Assert\Callback
public function validate(ExecutionContextInterface $context, $payload)
... lines 263 - 267

Let's make sure that the title of this Article doesn't contain the string the borg... cause they're scary. So, if stripos() of $this->getTitle() and the borg does not equal false... error! To create the error, use $context->buildViolation():

Um.. the Borg kinda makes us nervous

Apparently so nervous that I typed "the Bork" instead! Resistance to typos is futile...

Next, choose which field to attach the error to with ->atPath('title') and finish with ->addViolation(). That's it!

... lines 1 - 260
public function validate(ExecutionContextInterface $context, $payload)
if (stripos($this->getTitle(), 'the borg') !== false) {
$context->buildViolation('Um.. the Bork kinda makes us nervous')
... lines 269 - 270

Go back to our form, write an article about how you really want to join the borg and Create!

Got it! Custom validation logic with a custom error.

Next: let's talk a little more about how we can control the rendering of these fields. Because, right now, we're just sort of rendering them all at once... without much control over their look and feel.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.2.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.1.6
        "symfony/console": "^4.0", // v4.1.6
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.1.6
        "symfony/framework-bundle": "^4.0", // v4.1.6
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.1.6
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/twig-bundle": "^4.0", // v4.1.6
        "symfony/validator": "^4.0", // v4.1.6
        "symfony/web-server-bundle": "^4.0", // v4.1.6
        "symfony/yaml": "^4.0", // v4.1.6
        "twig/extensions": "^1.5" // v1.5.2
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.1.6
        "symfony/dotenv": "^4.0", // v4.1.6
        "symfony/maker-bundle": "^1.0", // v1.8.0
        "symfony/monolog-bundle": "^3.0", // v3.3.0
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.6
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.1.6