Валідація

29/03/2016 0 symfony, налаштування, конфігурація

Валідація (перевірка) - звичне завдання в додатках вебу. Дані, які поступають у формах, потребують валідації, так само як і інформація перед записом у базу даних, або у веб-сервіс. У цьому випадку, Symfony приходить на допомогу з компонентом Validator (валідатор), який набагато полегшує роботу. Цей компонент базується на JSR303 Bean Validation specification.

Основи валідації

Найкраще цей процес можна зрозуміти побачивши все на практиці. Для того, аби розпочати, уявимо, що ви створили простий PHP-об’єкт, який надалі буде використовуватися у вашому додатку:  

// src/AppBundle/Entity/Author.php
namespace AppBundle\Entity;

class Author
{
    public $name;
}

Це просто звичайний клас, який виконує деякі функції всередині вашого додатку. Ціль валідації - повідомити про те, чи дані об’єкта дійсні. Для роботи потрібно налаштувати список правил (constraints), яких об’єкт повинен дотримуватися для того, щоб бути дійсним. Ці правила можна подати у різних форматах (YAML, XML, анотації, чи PHP).

Наприклад, щоб переконатися що властивість $name не порожня, додайте наступне:  

Анотація
// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    /**
     * @Assert\NotBlank()
     */
    public $name;
}

YAML

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
    properties:
        name:
            - NotBlank: ~

XML

<!-- 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

// 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());
    }
}

Збережені та приватні налаштування також можуть підлягати процесу валідації, так як і методи “getter” (деталі у підрозділі “Цілі обмежень”)

У версії Symfony 2.7, файли з обмеженнями XML та Yaml розташовані у пакеті  Resources/config/validationsub-directory слід завантажувати. До цієї версії завантажувати потрібно було лише Resources/config/validation.yml (or.xml).

Використовуємо сервіс валідації (The Validator Service)

Далі, щоб зробити дійсним об’єкт Author, використайте метод validate у сервісі validator  (клас Validator). Робота сервісу validator дуже проста: прочитати обмеження (правила) класу і пересвідчитися чи дані об’єкту відповідають тим вимогам. Якщо процес валідації неуспішний, ви отримаєте список помилок (клас ConstraintViolationList). Перегляньте цей простий приклад з середини контролера:

// ...
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Author;

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

    // ... do something to the $author object

    $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 порожня, ви побачите наступне повідомлення:  

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

Якщо ви вставите значення у властивість name, ви отримаєте повідомлення про те, що все виконано успішно.  

Загалом, ви не працюватимете з сервісом validator напряму. Частіше всього, ви будете використовувати перевірку побічно при зверненні відправлених даних форми. Детальніше ви прочитаєте у наступному підрозділі “Валідація і форми”.

Також, ви можете перенести помилки у шаблон.  

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

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

Twig

{# 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

<!-- 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>

Кожна “validation error”, яку  називають "порушення обмеження", представлена об’єктом ConstraintViolation.

Валідація і форми

Сервіс validator може використовуватися у будь-який час для перевірки будь-якого об’єкта. Насправді, ви не працюєте напряму з об’єктом validator коли маєте справу з формами. Бібліотека форм Symfony використовує сервіс validator для перевірки базових об’єктів після того, як значення були надіслані. Порушення обмеження об’єкта конвертуються в об’єкти, які легко відображатимуться у вашій формі. Типовий робочий процес відправки форми зсередини контролера виглядає наступним чином:

// ...
use AppBundle\Entity\Author;
use AppBundle\Form\AuthorType;
use Symfony\Component\HttpFoundation\Request;

// ...
public function updateAction(Request $request)
{
    $author = new Author();
    $form = $this->createForm(AuthorType::class, $author);

    $form->handleRequest($request);

    if ($form->isValid()) {
        // the validation passed, do something with the $author object

        return $this->redirectToRoute(...);
    }

    return $this->render('author/form.html.twig', array(
        'form' => $form->createView(),
    ));
}

У цьому прикладі використано клас форми AuthorType, який тут не показано.

Більше інформації про це буде у наступному блозі “Форми”.

Налаштування

У Symfony валідатором можна користуватися по замовчуванню, проте вам слід чітко включити анотації, якщо ви використовуєте саме такий метод для специфікації обмежень.

YAML

# app/config/config.yml
framework:
   validation: { enable_annotations: true }
XML
<!-- 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

// app/config/config.php
$container->loadFromExtension('framework', array(
    'validation' => array(
        'enable_annotations' => true,
    ),
));

Обмеження

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

За лаштунками, обмеження - це просто PHP об’єкти, які створюють стверджувальні визначення. У повсякденному житті обмеженням, наприклад, може бути таке твердження “Пиріг не повинен згоріти”. У Symfony це діє подібним чином. Обмеження - це твердження, що умова дійсна. Якщо ви задасте значення, обмеження “розкажуть” чи те значення дотримується необхідних правил.  

Обмеження, які підтримуються

Symfony містить багато обмежень, якими часто користуються:  

Основні обмеження

Існують основні обмеження: ви можете користуватися ними, щоб перевірити базові речі про значення властивостей, або повернення значення методів вашого об’єкта.  

Послідовні обмеження

Числові обмеження

Порівняльні обмеження (Comparison Constraints)

Обмеження дати (Date Constraints)

Кількісні обмеження (Collection Constraints)

Файлові обмеження (File Constraints)

Фінансові та інші числові обмеження

Інші обмеження (Other Constraints)

Ви також можете створювати свої власні обмеження. Ця тема детальніше відкрита у статті довідника: “Як створити власні обмеження” (How to Create a custom Validation Constraint)

Налаштування обмежень

Деякі обмеження, наприклад NotBlank, дуже прості, в той час як інші, наприклад Choice, мають декілька опцій для налаштувань. Уявіть, що клас Author має властивість під назвою gender, який матиме значення "male", "female" або  "other":

Aнотації

// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    /**
     * @Assert\Choice(
     *     choices = { "male", "female", "other" },
     *     message = "Choose a valid gender."
     * )
     */
    public $gender;

    // ...
}

YAML

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
    properties:
        gender:
            - Choice: { choices: [male, female, other], message: Choose a valid gender. }
        # ...

XML

<!-- 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="gender">
            <constraint name="Choice">
                <option name="choices">
                    <value>male</value>
                    <value>female</value>
                    <value>other</value>
                </option>
                <option name="message">Choose a valid gender.</option>
            </constraint>
        </property>

        <!-- ... -->
    </class>
</constraint-mapping>

PHP

// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    public $gender;

    // ...

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        // ...

        $metadata->addPropertyConstraint('gender', new Assert\Choice(array(
            'choices' => array('male', 'female', 'other'),
            'message' => 'Choose a valid gender.',
        )));
    }
}

Опції обмежень можуть бути подані як масив. Деякі з них дозволяють використати одне значення, за замовчуванням, замість масиву. У випадку з обмеженням Choice, опції choices можна подати у такому вигляді:

Анотації

// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    /**
     * @Assert\Choice({"male", "female", "other"})
     */
    protected $gender;

    // ...
}

YAML

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
    properties:
        gender:
            - Choice: [male, female, other]
        # ...

XML

<!-- 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="gender">
            <constraint name="Choice">
                <value>male</value>
                <value>female</value>
                <value>other</value>
            </constraint>
        </property>

        <!-- ... -->
    </class>
</constraint-mapping>

PHP

// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    protected $gender;

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        // ...

        $metadata->addPropertyConstraint(
            'gender',
            new Assert\Choice(array('male', 'female', 'other'))
        );
    }
}

Ми зробили це таким чином для того, щоб конфігурація найпоширенішої опції, опції обмеження, стала коротшою і швидшою.

Якщо ви не знатимете, як вказати опцію, перевірте документацію API для обмеження, або створіть це у вигляді масивів опцій (перший метод був показаний зверху).

Переклад повідомлень з обмеженнями

Дізнатися більше про переклад повідомлень з обмеженнями ви зможете в одному з наступних розділів книжки.  

Мета обмежень  

Обмеження можуть застосовуватися до властивостей класу (наприклад, name), або до відкритого методу геттер (наприклад, getFullName). Перший метод вживається найчастіше, проте другий дає можливість задавати складніші правила валідації.

Властивості

Перевірка властивостей класу є найосновнішою технікою валідації. Symfony дозволяє перевіряти приватні, захищені або відкриті властивості. Наступний список показує вам як налаштувати властивість $firstName класу Author, щоб мати хоча б три характеристики.  

Анотація
// src/AppBundle/Entity/Author.php

// ...
use Symfony\Component\Validator\Constraints as Assert;

class Author
{
    /**
     * @Assert\NotBlank()
     * @Assert\Length(min=3)
     */
    private $firstName;
}

YAML

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
    properties:
        firstName:
            - NotBlank: ~
            - Length:
                min: 3

XML

<!-- 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

// 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:

Анотація

// 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

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\Author:
    getters:
        passwordLegal:
            - 'IsTrue': { message: 'The password cannot match your first name' }

XML

<!-- 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

// 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():   

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

Напевно ви помітили, що у відображенні пропущено префікс геттера   ("get", "is" or "has"). Пізніше, це допоможе переставити обмеження на властивість з таким самим іменем (або навпаки) не змінюючи логіку перевірки.

Класи

Деякі обмеження використовуються для перевірки цілого класу. Наприклад, обмеження Callback є загальним, і використовуються власне до класу. Коли клас перевірений, уточнені обмеженнями методи просто виконують свою функцію, щоб перевірка була більш зручнішою для користувача.  

Групи обмежень

До цього моменту ви могли добавляти обмеження до класу і запитувати чи клас проходить усі потрібні обмеження. У деяких випадках, вам слід буде перевірити об’єкт щодо деяких обмежень цього класу. Для цього згрупуйте усі обмеження у “групу обмежень”, і тоді, за допомогою цього, зробіть перевірку.

Уявімо, що ви створили клас User, який використовується у двох випадках, коли користувач реєструється, і коли він пізніше оновлює контактні дані:

Анотація

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

class User implements UserInterface
{
    /**
     * @Assert\Email(groups={"registration"})
     */
    private $email;

    /**
     * @Assert\NotBlank(groups={"registration"})
     * @Assert\Length(min=7, groups={"registration"})
     */
    private $password;

    /**
     * @Assert\Length(min=2)
     */
    private $city;
}

YAML

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\User:
    properties:
        email:
            - Email: { groups: [registration] }
        password:
            - NotBlank: { groups: [registration] }
            - Length: { min: 7, groups: [registration] }
        city:
            - Length:
                min: 2

XML

<!-- 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\User">
        <property name="email">
            <constraint name="Email">
                <option name="groups">
                    <value>registration</value>
                </option>
            </constraint>
        </property>

        <property name="password">
            <constraint name="NotBlank">
                <option name="groups">
                    <value>registration</value>
                </option>
            </constraint>
            <constraint name="Length">
                <option name="min">7</option>
                <option name="groups">
                    <value>registration</value>
                </option>
            </constraint>
        </property>

        <property name="city">
            <constraint name="Length">
                <option name="min">7</option>
            </constraint>
        </property>
    </class>
</constraint-mapping>

PHP

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;

class User
{
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('email', new Assert\Email(array(
            'groups' => array('registration'),
        )));

        $metadata->addPropertyConstraint('password', new Assert\NotBlank(array(
            'groups' => array('registration'),
        )));
        $metadata->addPropertyConstraint('password', new Assert\Length(array(
            'min'    => 7,
            'groups' => array('registration'),
        )));

        $metadata->addPropertyConstraint('city', new Assert\Length(array(
            "min" => 3,
        )));
    }
}

При такому налаштуванні виникає три групи обмежень:  

Default

Містить обмеження в поточному класі і всі пов'язані класи, які не належать до іншої групи.

User

Еквівалентний до усіх обмежень об’єкта User у групі Default. Цей клас завжди матиме таке ім’я. Різницю між ним і класом Default ми пояснимо пізніше.

registration

Містить обмеження лише у полях для електронної пошти та паролю.  

Обмеження у групі Default є такими обмеженнями які або не мають чітко налаштованої групи, або налаштовані до групи рівної імені класу або рядка Default.

Перевіряючи об’єкт User, не існує різниці між групою Default і User. Але ця різниця з’являється коли у групі User є об’єкти. Уявімо, що група User має властивість address, яка у свою чергу вміщає об’єкт Address, і що ви додали обмеження Valid до цієї властивості, для того, щоб вона вже була перевірена, коли ви перевірятимете об’єкт  User.

Якщо ви перевіряєте User використовуючи групу Default, тоді будуть використовуватися усі обмеження у класі  Address, які є у групі Default. Проте, якщо ви перевірятимете User, використовуючи групу User, тоді перевірятися будуть обмеження у класі  Address у групі User.

Іншими словами група Default і ім’я класу групи (наприклад, User) ідентичні, окрім випадку, коли клас включений у інший об’єкт, який вже був перевірений.  

Якщо у вас є успадкування (наприклад, User extends BaseUser) і ви перевіряєте з ім’ям класу підкласу (наприклад, User), тоді усі обмеження у User та BaseUser будуть перевірені. Проте, якщо ви перевіряєте, використовуючи базовий клас (наприклад,   BaseUser), тоді перевірятимуться лише обмеження за замовчуванням у класі BaseUser.

Для того, щоб вказати валідатору працювати з певною групою, використайте одну, чи більше імен груп в якості третього аргументу в методі validate():

// If you're using the new 2.5 validation API (you probably are!)
$errors = $validator->validate($author, null, array('registration'));

// If you're using the old 2.4 validation API, pass the group names as the second argument
// $errors = $validator->validate($author, array('registration'));

Якщо ви не вказали жодної групи, програма використовуватиме усі обмеження, які належать до групи Default. Звичайно, ви частіше працюватимете з валідацією через бібліотеку. Для того, щоб дізнатися більше інформації, як саме користуватися групами обмежень всередині форм, ви прочитаєте у наступному блозі про форми.

Послідовність груп

У деяких випадках, вам потрібно буде перевіряти групи послідовно. Для цього використовуйте GroupSequence. Тут об’єкт визначає послідовність груп, і ця послідовність також повинна бути перевірена. У деяких випадках все робити краще покроково. Наприклад, уявімо, що у вас є клас User і ви хотіли б перевірити чи ім’я користувача і пароль не є однаковими, тільки у випадку, коли всі інші перевірки виконалися (для того, щоб уникнути повідомлень про помилки).

Анотація

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @Assert\GroupSequence({"User", "Strict"})
 */
class User implements UserInterface
{
    /**
     * @Assert\NotBlank
     */
    private $username;

    /**
     * @Assert\NotBlank
     */
    private $password;

    /**
     * @Assert\IsTrue(message="The password cannot match your username", groups={"Strict"})
     */
    public function isPasswordLegal()
    {
        return ($this->username !== $this->password);
    }
}

YAML

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\User:
    group_sequence:
        - User
        - Strict
    getters:
        passwordLegal:
            - 'IsTrue':
                message: 'The password cannot match your username'
                groups: [Strict]
    properties:
        username:
            - NotBlank: ~
        password:
            - NotBlank: ~

XML

<!-- 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\User">
        <property name="username">
            <constraint name="NotBlank" />
        </property>

        <property name="password">
            <constraint name="NotBlank" />
        </property>

        <getter property="passwordLegal">
            <constraint name="IsTrue">
                <option name="message">The password cannot match your username</option>
                <option name="groups">
                    <value>Strict</value>
                </option>
            </constraint>
        </getter>

        <group-sequence>
            <value>User</value>
            <value>Strict</value>
        </group-sequence>
    </class>
</constraint-mapping>

PHP

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints as Assert;

class User
{
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('username', new Assert\NotBlank());
        $metadata->addPropertyConstraint('password', new Assert\NotBlank());

        $metadata->addGetterConstraint('passwordLegal', new Assert\IsTrue(array(
            'message' => 'The password cannot match your first name',
            'groups'  => array('Strict'),
        )));

        $metadata->setGroupSequence(array('User', 'Strict'));
    }
}

У даному прикладі, програма спочатку перевірятиме усі обмеження у групі User (які є такими самими, як і у групі Default). Лише коли усі обмеження у групі будуть перевірені, перевірятиметься наступна група, Strict.

Як ви вже дізналися з попереднього підрозділу, група Default і група з іменем класу (наприклад, User) були ідентичними. Проте, якщо ми працюватимемо з послідовністю груп (Group Sequences), вони більше не будуть однаковими. Група Default не посилатиметься на послідовність груп, замість усіх тих обмежень, які не належать до жодної з груп.  

Це означає, що вам слід використати групу {ClassName} (наприклад, User), коли ви вказуватимете на послідовність груп. Коли ви будете працювати з Default, ви отримаєте нескінченну рекурсію (оскільки, група Default посилається на послідовність груп, яка містить групу Default, яка у свою чергу посилається на ту саму послідовність груп,...).

Провайдери послідовності груп

Уявимо, що у нас є клас User, який може бути як звичайним так і преміум. Якщо це преміум користувач, слід додати декілька нових обмежень до класу (наприклад, деталі кредитної картки). Щоб швидко визначити які групи потрібно активувати, створимо провайдер послідовність груп (Group Sequence Provider). Для початку залучимо клас і нову групу обмежень під назвою Premium:

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class User
{
    /**
     * @Assert\NotBlank()
     */
    private $name;

    /**
     * @Assert\CardScheme(
     *     schemes={"VISA"},
     *     groups={"Premium"},
     * )
     */
    private $creditCard;

    // ...
}

YAML

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\User:
    properties:
        name:
            - NotBlank: ~
        creditCard:
            - CardScheme:
                schemes: [VISA]
                groups: [Premium]

XML

<!-- 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\User">
        <property name="name">
            <constraint name="NotBlank" />
        </property>

        <property name="creditCard">
            <constraint name="CardScheme">
                <option name="schemes">
                    <value>VISA</value>
                </option>
                <option name="groups">
                    <value>Premium</value>
                </option>
            </constraint>
        </property>

        <!-- ... -->
    </class>
</constraint-mapping>

PHP

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Mapping\ClassMetadata;

class User
{
    private $name;
    private $creditCard;

    // ...

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->addPropertyConstraint('name', new Assert\NotBlank());
        $metadata->addPropertyConstraint('creditCard', new Assert\CardScheme(
            'schemes' => array('VISA'),
            'groups'  => array('Premium'),
        ));
    }
}

Тепер, змініть клас User, щоб втілити GroupSequenceProviderInterface і додайте метод  getGroupSequence(), який повертає масив груп для користування: 

Анотація

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

// ...
use Symfony\Component\Validator\GroupSequenceProviderInterface;

class User implements GroupSequenceProviderInterface
{
    // ...

    public function getGroupSequence()
    {
        $groups = array('User');

        if ($this->isPremium()) {
            $groups[] = 'Premium';
        }

        return $groups;
    }
}

На сам кінець, вкажіть компонентові валідатора що клас User надає послідовність груп для перевірки:  

Анотація

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

// ...

/**
 * @Assert\GroupSequenceProvider
 */
class User implements GroupSequenceProviderInterface
{
    // ...
}

YAML

# src/AppBundle/Resources/config/validation.yml
AppBundle\Entity\User:
    group_sequence_provider: true

XML

<!-- 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\User">
        <group-sequence-provider />
        <!-- ... -->
    </class>
</constraint-mapping>

PHP

// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;

// ...
use Symfony\Component\Validator\Mapping\ClassMetadata;

class User implements GroupSequenceProviderInterface
{
    // ...

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
        $metadata->setGroupSequenceProvider(true);
        // ...
    }
}

Перевірка значень і масивів 

Тепер ви вже знаєте як можна перевіряти цілі об’єкти. Проте, інколи виникатиме потреба  просто перевірити значення, наприклад, чи певна строка є потрібною електронною адресою. Це все дуже просто зробити. Виглядатиме це ось таким чином з середини контролера:

// ...
use Symfony\Component\Validator\Constraints as Assert;

// ...
public function addEmailAction($email)
{
    $emailConstraint = new Assert\Email();
    // all constraint "options" can be set this way
    $emailConstraint->message = 'Invalid email address';

    // use the validator to validate the value
    // If you're using the new 2.5 validation API (you probably are!)
    $errorList = $this->get('validator')->validate(
        $email,
        $emailConstraint
    );

    // If you're using the old 2.4 validation API
    /*
    $errorList = $this->get('validator')->validateValue(
        $email,
        $emailConstraint
    );
    */

    if (0 === count($errorList)) {
        // ... this IS a valid email address, do something
    } else {
        // this is *not* a valid email address
        $errorMessage = $errorList[0]->getMessage();

        // ... do something with the error
    }

    // ...
}

Давши команду на виконання методу validate у валідаторі, ви можете передати йому значення у вигляді параметра і об’єкт обмеження, який бажаєте перевірити.  Список доступних обмежень, а також і повне ім’я класу для кожного обмеження, подані тут.  

Метод перевірки повертатиме об’єкт ConstraintViolationList, який дає результат подібний до масиву, який містить помилки. Кожна з помилок є об’єктом ConstraintViolation, яка містить повідомлення про помилку у методі getMessage.

Підсумки

Валідатор Symfony - потужний інструмент, що допомагає “перевірити” (валідувати) дані усіх об’єктів. Потужність процесу валідації полягає у “обмеженнях”, тобто правилах, які використовуються до властивостей або геттерів вашого об’єкта.  

Під час роботи з формами, ви не будете користуватися цим фреймворком напряму, але ви можете користуватися ним будь-де для валідаціїї об’єктів.

Дізнавайтесь більше у Довіднику.

Поділитися