Symfony Validation with the Validator

In the previous blog post I talked about the differences between exceptions and a validator. We know that on the domain layer (Entities) you should use simple exceptions. But we don’t want to show these exceptions to the clients of our system.

What we want to do is show clear and useful validation messages to our clients. We don’t want to show them one ugly exception message. The single most important purpose of validation is to inform the clients that the data they entered is wrong, and why.

Instead of creating our own custom validator component, we can use the Symfony Validator. Lets discover how we can use it.

Using the Validator

The first step is to figure out how to use the validator component in its simplest version. Without a form, request object or Entity.

But first, how do we start?

Do we need an object to validate on, or can we just create some validation definitions and apply it on an array?

I tried both and I will be honest. Using the Symfony Validator as standalone for validating on an array does not work as well as you expect. It also clearly states it in the documentation.

The validator is designed to validate objects against constraints (i.e. rules).

An object to validate

So we need to create an object. We could use an Entity, and all examples that I use from here, would also work if the object is an Entity. But I will be using a DTO object instead. The ProductDTO.

final class ProductDTO
{

    /**
     * @var Uuid
     */
    private $id;

    /**
     * @var string
     */
    private $name;

    /**
     * @var float
     */
    private $price;

    /* Getters and Setters here */

}

In some systems you want different validations depending on the request.

Well that means creating different DTO objects. Every DTO is a different kind of request requiring its own validation.

Installing the Validator Component

If you are using Symfony flex you can install the Validator component with the following command:

composer require validator

And we also need to install the property access package. This is required for accessing properties within comparison constraints.

composer require

symfony/property-access

Now we can create our constraints.

Validation Constraints

Validating something means using constraints.

Constraint is a limitation or restriction. This ensures the accuracy and reliability of the data.

There are 2 distinct solutions to create constraints for an object. Either you use Annotations inside the object or you create a seperate Validation file (yaml or xml). I will show you both.

Annotations

Open your object class file and at use Symfony\Component\Validator\Constraints as Assert; at the top of your file. With this you can use all the Validator Constraints from the Assert keyword.

use Symfony\Component\Validator\Constraints as Assert;

final class ProductDTO
{

    /**
     * @var Uuid
     */
    private $id;

    /**
     * @Assert\NotBlank
     * @var string
     */
    private $name;

    /**
     * @Assert\NotBlank
     * @Assert\GreaterThan(0)
     * @var float
     */
    private $price;

}

Now this class has 2 concerns. If we ought to be strict, then validation is and should be a separate concern, and be place in a separate file. This breaks the Single Responsibility Principle.

Yaml

An alternative is to use Yaml or Xml instead of Annotations. For this example I will be using Yaml.

First you need to create a new file config/validator/validation.yaml. In here you can then use Yaml to use the same Validation Constraints as defined in our object.

App\Application\DTO\ProductDTO:
    properties:
        name:
            - NotBlank: ~
        price:
            - NotBlank: ~
            - GreaterThan:
                value: 0

As you notice, it’s a bit different. More descriptive. And we also need to define the class name of our object.

When the Validator component can’t find Annotations on your object it will read out the validation.yaml file and look for the class name of the object its trying to validate.

You could also use XML instead of Yaml, whatever you prefer.

Here you can find a list of all Constraints and how to use them.

Validating

Then actually doing the validation is pretty simple. We need to inject the ValidatorInterface and then pass our object to validate in the validate method.

public function productDTOValidation(ValidatorInterface $validator): Response
    {
        $productDTO = new ProductDTO('Product 1', 0);

        $violations = $validator->validate($productDTO);

        if (\count($violations) > 0) {
            $violationsString = (string) $violations;

            /* Show our violations */
        }
    }

The violationsString will now contain your current violations as a string.

Object(App\Application\DTO\ProductDTO).price: This value should be greater than 0. (code 778b7ae0-84d3-481a-9dec-35fdb64b1d78)

You can then normalize or manipulate the $violations collections as you see fit.

Conclusion

In the upcoming blog posts we can build on this. We now know the basics of the Validator Component. And we can make a choice between Annotations, Yaml or XML. Changing where and which violations to use is something that is always separated from doing the actual validation. So this can always be changed if needed.