Как создать пользовательское ограничение валидации

Как создать пользовательское ограничение валидации

Вы можете создать пользовательское ограничение, расширив базовый класс ограничения Constraint. В качестве приера вы создадите простой валидатор, который проверяет, содержит ли строка только буквенно-цифровые знаки.

Создание класса ограничения

Для начала вам нужно создать класс ограничения (Constraint) и расширить Constraint:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// src/AppBundle/Validator/Constraints/ContainsAlphanumeric.php
namespace AppBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class ContainsAlphanumeric extends Constraint
{
    public $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.';
}

Note

Аннотация @Annotation необходима для этого нового ограничения для того, чтобы сделать его доступным для использования в классах через аннотации. Опции вашего ограничения представлены в качестве публичных свойств класса ограничения.

Создание самого валидатора

Как вы видите, класс ограничения достаточно минимален. Сама валидация выполняется другим классом "валидатором ограничения". Класс валидатора ограничения указывается методом ограничения validatedBy(), который включает некоторую простую логику по умолчанию:

1
2
3
4
5
// в базовом классе Symfony\Component\Validator\Constraint
public function validatedBy()
{
    return get_class($this).'Validator';
}

Другими словами, если вы создадите пользовательское Constraint (например, MyConstraint), Symfony автоматически будет искать другой класс, MyConstraintValidator при проведении самой валидации.

Класс валидатора так же прост, и имеет только один обязательный метод validate():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// src/AppBundle/Validator/Constraints/ContainsAlphanumericValidator.php
namespace AppBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class ContainsAlphanumericValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint)
    {
        if (!preg_match('/^[a-zA-Z0-9]+$/', $value, $matches)) {
            $this->context->buildViolation($constraint->message)
                ->setParameter('{{ string }}', $value)
                ->addViolation();
        }
    }
}

Внутри validate вам не нужно возвращать значение. Вместо этого, вы добавляете нарушения к свойству валидатора context и значение будет принято как валидное, если оно не вызывает никаких нарушений. Метод buildViolation() берёт сообщение об ошибке в качестве своего аргумента и возвращает экземпляр ConstraintViolationBuilderInterface. Метод вызова addViolation() в конце-концов добавляет наружение в контекст.

Использование нового валидатора

Использовать пользовательские валидаторы очень просто, так же, как и те, что предоставляются самим Symfony:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    // src/AppBundle/Entity/AcmeEntity.php
    use Symfony\Component\Validator\Constraints as Assert;
    use AppBundle\Validator\Constraints as AcmeAssert;
    
    class AcmeEntity
    {
        // ...
    
        /**
         * @Assert\NotBlank
         * @AcmeAssert\ContainsAlphanumeric
         */
        protected $name;
    
        // ...
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    # src/AppBundle/Resources/config/validation.yml
    AppBundle\Entity\AcmeEntity:
        properties:
            name:
                - NotBlank: ~
                - AppBundle\Validator\Constraints\ContainsAlphanumeric: ~
    
  • 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\AcmeEntity">
            <property name="name">
                <constraint name="NotBlank" />
                <constraint name="AppBundle\Validator\Constraints\ContainsAlphanumeric" />
            </property>
        </class>
    </constraint-mapping>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/AppBundle/Entity/AcmeEntity.php
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints\NotBlank;
    use AppBundle\Validator\Constraints\ContainsAlphanumeric;
    
    class AcmeEntity
    {
        public $name;
    
        public static function loadValidatorMetadata(ClassMetadata $metadata)
        {
            $metadata->addPropertyConstraint('name', new NotBlank());
            $metadata->addPropertyConstraint('name', new ContainsAlphanumeric());
        }
    }
    

Если ваше ограничение содержит опции, то они должны быть публичными свойствами пользовательского класса ограничения, который вы создали ранее. Эти опции могут быть сконфигурированы как опции базовых ограничений Symfony.

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

Если вы используете конфигурацию services.yml по умолчанию, то ваш валидатор уже зарегистрирован в качестве сервиса и тегирован необходимым validator.constraint_validator. Это означает, что вы можете внедрять сервисы или конфигурацию, как любой другой сервис.

Валидатор класса ограничений

Кроме валидации свойства класса, ограничение может иметь область действия класса, при определении цели в его классе Constraint:

1
2
3
4
public function getTargets()
{
    return self::CLASS_CONSTRAINT;
}

С помощью этого, метод валидатора validate() получает объект в качестве своего первого аргумента:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class ProtocolClassValidator extends ConstraintValidator
{
    public function validate($protocol, Constraint $constraint)
    {
        if ($protocol->getFoo() != $protocol->getBar()) {
            $this->context->buildViolation($constraint->message)
                ->atPath('foo')
                ->addViolation();
        }
    }
}

Отметьте, что валидатор класса ограничения применяется к самому классу, а не к свойству:

  • Annotations
    1
    2
    3
    4
    5
    6
    7
    /**
     * @AcmeAssert\ContainsAlphanumeric
     */
    class AcmeEntity
    {
        // ...
    }
    
  • YAML
    1
    2
    3
    4
    # src/AppBundle/Resources/config/validation.yml
    AppBundle\Entity\AcmeEntity:
        constraints:
            - AppBundle\Validator\Constraints\ContainsAlphanumeric: ~
    
  • XML
    1
    2
    3
    4
    <!-- src/AppBundle/Resources/config/validation.xml -->
    <class name="AppBundle\Entity\AcmeEntity">
        <constraint name="AppBundle\Validator\Constraints\ContainsAlphanumeric" />
    </class>
    

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