Валидация объектов и их свойств в Symfony 3.4

В Symfony есть встроенные валидаторы данных. Окей, а вы уверены что они работают и валидируют по всем правилам? Столкнулся с ситуацией, в которой валидароты скипали правила описанные в аннотациях объектов. Давайте покажу на примере. Допустим есть класс с пропертями, геттерами и сеттерами, т.е. типичный Data Transfer Object (DTO).

// src/Entity/Author.php
namespace App\Entity;
class Author
{
    public $name;
}

А теперь навесим на него немного правил валидации (constraints):

// src/Entity/Author.php
// ...
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
    /**
     * @Assert\NotBlank()
     */
    public $name;
}

Теперь давайте немного повалидируем:

// ...
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use App\Entity\Author;
// ...
public function author(ValidatorInterface $validator)
{
    $author = new Author();
    // ... do something to the $author object
    $errors = $validator->validate($author);
    if (count($errors) > 0) {
        /*
         * Uses a __toString method on the $errors variable which is a
         * ConstraintViolationList object. This gives us a nice string
         * for debugging.
         */
        $errorsString = (string) $errors;
        return new Response($errorsString);
    }
    return new Response('The author is valid! Yes!');
}

Как думаете, провалидирует ли Symfony поля объекта? Всё зависит от того, как сконфигурирован валидатор и читает ли он мета-данные объектов. Если используется дефолтный фабричный метод:

Validation::createValidator();

То мне придётся вас огорчить, значения объектов валидироваться не будут! Это произойдёт потому, что валидатор создаётся без маппера аннотаций. Смотрим код vendor\symfony\symfony\src\Symfony\Component\Validator\Validation.php

/**
 * Entry point for the Validator component.
 *
 * @author Bernhard Schussek <[email protected]>
 */
final class Validation
{
    /**
     * Creates a new validator.
     *
     * If you want to configure the validator, use
     * {@link createValidatorBuilder()} instead.
     *
     * @return ValidatorInterface The new validator
     */
    public static function createValidator()
    {
        return self::createValidatorBuilder()->getValidator();
    }
    /**
     * Creates a configurable builder for validator objects.
     *
     * @return ValidatorBuilderInterface The new builder
     */
    public static function createValidatorBuilder()
    {
        return new ValidatorBuilder();
    }
}

Создать переконфигурированный валидатор можно так:

$validator = (new ValidatorBuilder())->enableAnnotationMapping()->getValidator();

И теперь можно вызывать валидацию объекта, только ни в коем случае нельзя передавать дополнительный параметр с массивом констрейтов, иначе валидатор возьмёт их, а не будет проверять по аннтоациям!

Ссылки для подробного изучения:

  • https://symfony.com/doc/current/validation.html
  • https://symfony.com/doc/3.4/components/validator/resources.html
  • https://symfony.com/doc/3.4/components/validator/metadata.html