Валидация

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

Symfony2 содержит компонент Validator для того, чтобы упростить эту задачу. Этот компонент основан на документе JSR303 Bean Validation specification. Что?! Java спецификация в PHP? Однако же вы всё услышали верно, но всё не так плохо как вам могло показаться. Давайте посмотрим, как мы можем использовать это в PHP.

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

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

<?php
// src/Acme/BlogBundle/Entity/Author.php
namespace Acme\BlogBundle\Entity;

class Author
{
    public $name;
}

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

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

Tip

Защищённые (protected) и закрытые (private) члены класса могут быть также валидированы как и любой get-метод (см. validator-constraint-targets).

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

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

<?php
use Symfony\Component\HttpFoundation\Response;
use Acme\BlogBundle\Entity\Author;
// ...

public function indexAction()
{
    $author = new Author();
    // ... выполняются какие-либо действия с объектом $author

    $validator = $this->get('validator');
    $errors = $validator->validate($author);

    if (count($errors) > 0) {
        return new Response(print_r($errors, true));
    } else {
        return new Response('The author is valid! Yes!');
    }
}

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

Acme\BlogBundle\Author.name:
    This value should not be blank

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

Tip

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

Вы также можете передать перечень ошибок в шаблон.

<?php
if (count($errors) > 0) {
    return $this->render('AcmeBlogBundle:Author:validate.html.twig', array(
        'errors' => $errors,
    ));
} else {
    // ...
}

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

Note

Каждая ошибка валидации (называемая “constraint violation”), представлена объектом класса Symfony\Component\Validator\ConstraintViolation

Валидация и Формы

Сервис validator может быть использован в любое время для проверки объекта. В жизни же, не смотря такую возможность, вы будете работать с сервисом validator косвенно при обработке форм. Библиотека форм Symfony использует сервис валидации для проверки объектов форм после того, как данные были отправлены пользователем и привязаны к форме. Объекты ошибок валидации (“constraint violations”) будут конвертированы в объекты FieldError, которые могут быть легко отображены вместе с формами. Типичный процесс отправки формы со стороны контроллера выглядит так:

<?php
use Acme\BlogBundle\Entity\Author;
use Acme\BlogBundle\Form\AuthorType;
use Symfony\Component\HttpFoundation\Request;
// ...

public function updateAction(Request $request)
{
    $author = new Acme\BlogBundle\Entity\Author();
    $form = $this->createForm(new AuthorType(), $author);

    if ($request->getMethod() == 'POST') {
        $form->bindRequest($request);

        if ($form->isValid()) {
            // валидация прошла успешно, можно выполнять дальнейшие действия с объектом $author

            $this->redirect($this->generateUrl('...'));
        }
    }

    return $this->render('BlogBundle:Author:form.html.twig', array(
        'form' => $form->createView(),
    ));
}

Note

Этот пример использует класс формы AuthorType, который в этой главе не описан.

Более подробную информацию о формах вы можете получить в главе Формы.

Конфигурирование

Валидатор Symfony2 доступен по умолчанию, но вы должны тщательно настроить его при помощи аннотаций (если вы используете метод аннотаций для настройки ограничений):

Ограничения

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

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

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

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

Вы также можете создавать свои ограничения. Этот вопрос освещается в топике “/cookbook/validation/custom_constraint” в книге рецептов.

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

Некоторые ограничения, как например NotBlank, просты, в то время как другие, как например Choice, имеют много различных опций. Предположим, что класс Author имеет поле gender, которое может иметь два значения - “male” или “female”:

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

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

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

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

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

Поля класса

Валидация полей класса - это наиболее простая техника валидации. Symfony2 позволяет вам выполнять валидацию приватных, защищённых и публичных полей. Следующий листинг показывает как настроить поле $firstName класса Author, чтобы оно имело как минимум три символа.

Методы класса

Ограничения также могут быть применены к значениям, возвращаемым методами. Symfony2 позволяет вам добавлять ограничения к любому публичному методу, если его имя начинается с “get” или “is”. В этом руководстве оба этих типа методов называются “геттерами” (от getters).

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

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

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

Note

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

Классы

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

Валидационные группы

До сих пор вы могли добавлять ограничения к классу и узнавать, удовлетворяет ли класс всем указанным для него ограничениям или же нет. В некоторых случаях, однако, вам может потребоваться валидировать объект, используя лишь некоторые из определённых для него ограничений. Для того чтобы получить такую возможность, вы можете определить каждое ограничение в одну или более “валидационных групп” и после этого выполнять валидацию лишь для одной из этих групп.

Например, положим у вас есть класс User, который используется при регистрации пользователя и при обновлении его профайла впоследствии:

При использовании такой конфигурации имеется две валидационные группы:

  • Default - содержит ограничения, не включённые ни в одну из групп;
  • registration - содержит ограничения для полей email и password.

Для того, чтобы явно указать валидатору группу, передайте одно или более наименований групп вторым аргументом в метод validate():

$errors = $validator->validate($author, array('registration'));

Конечно же, как правило, вы работаете с валидацией косвенно через библиотеку Форм. Чтобы узнать как использовать группы в формах, смотрите раздел Валидационные группы.

Валидация простых значений и массивов

Ранее вы увидели как можно валидировать целые объекты. Но иногда, вам всего лишь нужно валидировать простое значение - например, проверить, является ли строка валидным email-адресом. Это также легко сделать. Внутри контроллера это будет выглядеть так:

<?php
// add this to the top of your class
use Symfony\Component\Validator\Constraints\Email;

public function addEmailAction($email)
{
    $emailConstraint = new Email();
    // все опции ограничения можно задать таким образом
    $emailConstraint->message = 'Invalid email address';

    // используем валидатор для проверки значения
    $errorList = $this->get('validator')->validateValue($email, $emailConstraint);

    if (count($errorList) == 0) {
        // это ВАЛИДНЫЙ адрес, делаем дело дальше
    } else {
        // это НЕ валидный адрес
        $errorMessage = $errorList[0]->getMessage()

        // делаем что-то с ошибкой
    }

    // ...
}

Вызывая метод validateValue в валидаторе, вы можете передать ему значение в виде параметра и объект ограничения, которое хотите проверить. Полный список доступных ограничений, а также полные имена классов для каждого ограничения, можно найти в Справочнике по ограничениям.

Метод validateValule возвращает объект класса Symfony\Component\Validator\ConstraintViolationList, который, по сути, является массивом ошибок. Каждая ошибка в коллекции - это объект класса Symfony\Component\Validator\ConstraintViolation, который содержит сообщение об ошибке, которое можно получить, вызвав метод getMessage.

Заключение

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

Дополнительно в книге рецептов:

  • /cookbook/validation/custom_constraint