Валидация

Валидация

Валидация - это очень распространенная задача для web-приложения. Данные, вводимые в формы, должны быть валидированы (проверены). В то же время, данные должны быть валидированы до того, как они будут записаны в базу данных или же будут переданы веб-сервису.

Symfony содержит компонент Validator, который упрощает эту задачу. Этот компонент основан на документе JSR303 Bean Validation specification.

Основы валидации

Самый лучший способ понять валидацию - это увидеть её в действии. Для начала, предположим, что вы создали обычный PHP-объект, который вам нужно использовать в вашем приложении:

1
2
3
4
5
6
7
// src/AppBundle/Entity/Author.php
namespace AppBundle\Entity;

class Author
{
    public $name;
}

Пока это обычный класс, который служит какой-то цели внутри вашего приложения. Задача валидации заключается в том, чтобы сообщить вам - являются ли данные объекта корректными (валидными). Для этого, вам нужно настроить перечень правил (называемых ограничениями (constraints)), которым объект должен соответствовать, чтобы быть валидным. Эти правила могут быть указаны во многих форматах (YAML, XML, аннотациях, или PHP).

Например, для того, чтобы гарантировать, что свойство $name не пустое, добавьте следующий код:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        /**
         * @Assert\NotBlank()
         */
        public $name;
    }
    
  • YAML
    1
    2
    3
    4
    5
    # src/AppBundle/Resources/config/validation.yml
    AppBundle\Entity\Author:
        properties:
            name:
                - NotBlank: ~
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- src/AppBundle/Resources/config/validation.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
            http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
    
        <class name="AppBundle\Entity\Author">
            <property name="name">
                <constraint name="NotBlank" />
            </property>
        </class>
    </constraint-mapping>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints\NotBlank;
    
    class Author
    {
        public $name;
    
        public static function loadValidatorMetadata(ClassMetadata $metadata)
        {
            $metadata->addPropertyConstraint('name', new NotBlank());
        }
    }
    

Tip

Защищенные (protected) и приватные (private) свойства тоже могут быть валидированы, так же, как и геттер-методы (см. Цель ограничений).

Использование сервиса validator

Далее, чтобы проверить объект Author, используйте метод validate() сервиса validator (класс Validator). Обязанности у validator простые: прочитать ограничения (т.е. правила) для класса, и определить, соответствуют ли данные из объекта этим ограничениям. Если валидация проходит с ошибкой, возвращается список ошибок (класс ConstraintViolationList). Давайте рассмотрим этот простой пример изнутри контроллера:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ...
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Author;

// ...
public function authorAction()
{
    $author = new Author();

    // ... сделать что-то с объектом $author

    $validator = $this->get('validator');
    $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!');
}

Если свойство $name пустое, вы увидите следующее сообщение об ошибке:

1
2
AppBundle\Entity\Author.name:
    This value should not be blank

Если же вы укажете значение для свойства name, появится сообщение об успешной валидации.

Tip

В большинстве случаев, вы не будете напрямую взаимодействовать с сервисом validator и вам не нужно будет беспокоиться об отображении ошибок. Зачастую вы будете использовать валидацию косвенно при обработке данных из отправленных приложению форм. Подробнее об этом написано в разделе Валидация и формы.

Вы также можете передать совокупность ошибок в шаблон:

1
2
3
4
5
if (count($errors) > 0) {
    return $this->render('author/validation.html.twig', array(
        'errors' => $errors,
    ));
}

Внутри шаблона вы можете отобразить список ошибок так, как вам нужно:

  • Twig
    1
    2
    3
    4
    5
    6
    7
    {# app/Resources/views/author/validation.html.twig #}
    <h3>The author has the following errors</h3>
    <ul>
    {% for error in errors %}
        <li>{{ error.message }}</li>
    {% endfor %}
    </ul>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    <!-- app/Resources/views/author/validation.html.php -->
    <h3>The author has the following errors</h3>
    <ul>
    <?php foreach ($errors as $error): ?>
        <li><?php echo $error->getMessage() ?></li>
    <?php endforeach ?>
    </ul>
    

Note

Каждая ошибка валидации (называемая «нарушение ограничения» "constraint violation"), представлена объектом ConstraintViolation.

Конфигурация

Перед тем, как использовать валидатор Symfony, убедитесь в том, что он активирован в глафном файле конфигурации:

  • YAML
    1
    2
    3
    # app/config/config.yml
    framework:
        validation: { enabled: true }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <framework:config>
            <framework:validation enabled="true" />
        </framework:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        'validation' => array(
            'enabled' => true,
        ),
    ));
    

Кроме того, если вы планируете использовать аннотации для конфигруации валидации, замените предыдущую конфигурацию следующим:

  • YAML
    1
    2
    3
    # app/config/config.yml
    framework:
        validation: { enable_annotations: true }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- app/config/config.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <framework:config>
            <framework:validation enable-annotations="true" />
        </framework:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    // app/config/config.php
    $container->loadFromExtension('framework', array(
        'validation' => array(
            'enable_annotations' => true,
        ),
    ));
    

Ограничения

Validator создан для того, чтобы проверять объекты на соответствие ограничениям (т.е. правилам). Для того чтобы валидировать объект, просто укажите для его класса одно или несколько ограничений, и передайте его сервису валидации (validator).

По сути, ограничение - это просто PHP-объект, который выполняет проверочное выражение. В настоящей жизни, ограничение может выглядеть так: "пирог не должен подгореть". В Symfony ограничения выглядят похожим образом: это утверждения, что некоторое условие истинно. Учитывая значение, ограничение скажет вам, соответствует ли это значение правилам ограничения.

Поддерживаемые ограничения

Symfony содержит большое количество самых необходимых ограничений:

Basic Constraints

These are the basic constraints: use them to assert very basic things about the value of properties or the return value of methods on your object.

String Constraints

Number Constraints

Date Constraints

File Constraints

Financial and other Number Constraints

Other Constraints

Вы также можете создавать свои собственные ограничения. Эта тема раскрыта в статье Индивидуальные ограничения

Конфигурация ограничений

Некоторые ограничения, как например NotBlank просты, в то время как другие, например Choice - имеют несколько опций конфигурации. Предположим, что класс Author имеет свойство genre (жанр), которое определяет жанр литератры, который в основном ассоциируется с автором, и которое можно установить в значение "беллетристика" или "не беллетристика" ("fiction" или "non-fiction"):

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        /**
         * @Assert\Choice(
         *     choices = { "fiction", "non-fiction" },
         *     message = "Choose a valid genre."
         * )
         */
        public $genre;
    
        // ...
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    # src/AppBundle/Resources/config/validation.yml
    AppBundle\Entity\Author:
        properties:
            genre:
                - Choice: { choices: [fiction, non-fiction], message: Choose a valid genre. }
            # ...
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!-- src/AppBundle/Resources/config/validation.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
            http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
    
        <class name="AppBundle\Entity\Author">
            <property name="genre">
                <constraint name="Choice">
                    <option name="choices">
                        <value>fiction</value>
                        <value>non-fiction</value>
                    </option>
                    <option name="message">Choose a valid genre.</option>
                </constraint>
            </property>
    
            <!-- ... -->
        </class>
    </constraint-mapping>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        public $genre;
    
        // ...
    
        public static function loadValidatorMetadata(ClassMetadata $metadata)
        {
            // ...
    
            $metadata->addPropertyConstraint('genre', new Assert\Choice(array(
                'choices' => array('fiction', 'non-fiction'),
                'message' => 'Choose a valid genre.',
            )));
        }
    }
    

Опции ограничения всегда могут быть переданы в виде массива. Однако, некоторые ограничения также позволяют вам передать значение одной опции «по умолчанию» вместо массива. В случае с ограничением Choice, опции choices можно указать таким образом.

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        /**
         * @Assert\Choice({"fiction", "non-fiction"})
         */
        protected $genre;
    
        // ...
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    # src/AppBundle/Resources/config/validation.yml
    AppBundle\Entity\Author:
        properties:
            genre:
                - Choice: [fiction, non-fiction]
            # ...
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!-- src/AppBundle/Resources/config/validation.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
            http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
    
        <class name="AppBundle\Entity\Author">
            <property name="genre">
                <constraint name="Choice">
                    <value>fiction</value>
                    <value>non-fiction</value>
                </constraint>
            </property>
    
            <!-- ... -->
        </class>
    </constraint-mapping>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        protected $genre;
    
        public static function loadValidatorMetadata(ClassMetadata $metadata)
        {
            // ...
    
            $metadata->addPropertyConstraint(
                'genre',
                new Assert\Choice(array('fiction', 'non-fiction'))
            );
        }
    }
    

Такая возможность позволяет сделать настройку самых распространенных опций ограничения короче и быстрее.

Если вы не уверены, как нужно указывать опцию, сверьтесь с документацией API для ограничения или же поступайте безопасно - всегда передавая массив опций (как показано в первом примере выше).

Ограничения в классах формы

Ограничения могут быть обозначены во время построения форм с помощью опции constraints полей формы:

1
2
3
4
5
6
7
8
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('myField', TextType::class, array(
            'required' => true,
            'constraints' => array(new Length(array('min' => 3)))
        ))
}

Опция constraints доступна только в случае, если ValidatorExtension была активирована в постройщике формы:

1
2
3
4
Forms::createFormFactoryBuilder()
    ->addExtension(new ValidatorExtension(Validation::createValidator()))
    ->getFormFactory()
;

Цели ограничения

Ограничения могут быть применены к свойству класса (например, name), публичному геттер-методу (например, getFullName()) или целому классу. Первый вариант наиболее распространенный и легкий. Однако ограничения геттера позволяют вам указывать более сложные правила валидации. И, наконец, ограничения класса предназначены для случаев, когда вы хотите валидировать класс в качестве единого целого.

Свойства

Валидация свойств класса - самая простая техника валидации. Symfony позволяет вам проверять приватные, защищенные или публичные свойства. Ниже вы увидите, как сделать так, чтобы свойство $firstName класса Author имело как минимум 3 символа.

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        /**
         * @Assert\NotBlank()
         * @Assert\Length(min=3)
         */
        private $firstName;
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    7
    # src/AppBundle/Resources/config/validation.yml
    AppBundle\Entity\Author:
        properties:
            firstName:
                - NotBlank: ~
                - Length:
                    min: 3
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- src/AppBundle/Resources/config/validation.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
            http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
    
        <class name="AppBundle\Entity\Author">
            <property name="firstName">
                <constraint name="NotBlank" />
                <constraint name="Length">
                    <option name="min">3</option>
                </constraint>
            </property>
        </class>
    </constraint-mapping>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        private $firstName;
    
        public static function loadValidatorMetadata(ClassMetadata $metadata)
        {
            $metadata->addPropertyConstraint('firstName', new Assert\NotBlank());
            $metadata->addPropertyConstraint(
                'firstName',
                new Assert\Length(array("min" => 3))
            );
        }
    }
    

Геттеры

Ограничения также могут применяться для того, чтобы вернуть значение метода. Symfony позволяет вам добавлять ограничение к любому публичному методу, имя которого начинается с «get», «is» или «has». В этой книге, подобные методы называются общим словом «геттеры».

Преимуществом этой техники является то, что она позволяет вам валидировать ваш объект динамично. Например, представьте, что вам нужно убедиться, что поле пароля не совпадает с именем пользователя (из соображений безопасности). Вы можете сделать это создав метод isPasswordLegal() и указав, что этот метод должен вернуться как true:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        /**
         * @Assert\IsTrue(message = "The password cannot match your first name")
         */
        public function isPasswordLegal()
        {
            // ... return true or false
        }
    }
    
  • YAML
    1
    2
    3
    4
    5
    # src/AppBundle/Resources/config/validation.yml
    AppBundle\Entity\Author:
        getters:
            passwordLegal:
                - 'IsTrue': { message: 'The password cannot match your first name' }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- src/AppBundle/Resources/config/validation.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
            http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
    
        <class name="AppBundle\Entity\Author">
            <getter property="passwordLegal">
                <constraint name="IsTrue">
                    <option name="message">The password cannot match your first name</option>
                </constraint>
            </getter>
        </class>
    </constraint-mapping>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/AppBundle/Entity/Author.php
    
    // ...
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints as Assert;
    
    class Author
    {
        public static function loadValidatorMetadata(ClassMetadata $metadata)
        {
            $metadata->addGetterConstraint('passwordLegal', new Assert\IsTrue(array(
                'message' => 'The password cannot match your first name',
            )));
        }
    }
    

Теперь создайте метод isPasswordLegal() и добавьте его в нужную вам логику:

1
2
3
4
public function isPasswordLegal()
{
    return $this->firstName !== $this->password;
}

Note

Самые внимательные из вас заметили, что префикс геттера («get», «is» или «has») опущен при отображении. Это позволит вам применить ограничение к свойству с таким же именем позже ( или наоборот), не изменяя логики валидации.

Классы

Некоторые ограничения применяются к целому валидируемому классу. Например ограничение Callback (возвратный вызов) - это универсальное ограничение, которое применяется к самому классу. Когда этот класс валидируется, методы, указанные ограничением, просто выполняются, что позволяет проводить более избирательную валидацию.

Заключение

Validator Symfony - это мощный инструмент, который используется для получения гарантий, что данные некоторого объекта "валидные". Сила валидации - в ограничениях, которые являются правилами, которые вы можете применить к свойствам или геттер-методам вашего объекта. И несмотря на то, что вы большей частью будете использовать фреймворк валидации косвенно при использовании форм, помните, что он может быть использован где угодно для валидации любого объекта.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.