В предыдущей статье этой серии мы начали знакомство с API-интерфейсами Entity Validation и Typed Data. Мы видели, как плагины DataType
взаимодействуют с определениями данных и как различные ограничения могут быть добавлены к последним на нескольких уровнях и точках расширения.
В этой части мы рассмотрим аспект фактической проверки и обработки нарушений. Кроме того, мы напишем наши собственные ограничения и валидатор, чтобы мы могли использовать пользовательские поведения в процессе проверки данных.
Проверка и обработка нарушений
Несмотря на то, что мы еще точно не знаем, как создаются ограничения, мы видели, как их можно добавлять в определения типизированных данных, включая поля сущностей. Давайте теперь посмотрим, как мы можем проверить сущности и обработать возможные нарушения, которые мы обнаружим.
Говоря о типизированных данных, мы уже видели, как можно вызывать метод validate()
экземпляра плагина DataType
который содержит определение данных. Когда дело касается сущностей, это может происходить как на уровне сущности, так и на уровне поля.
Например, мы можем проверить всю сущность, используя метод validate()
:
$entity - > set ( 'title' , 'this is too long of a title' ) ; $violations = $entity - > validate ( ) ;
В нашей предыдущей статье мы добавили ограничение Length
к заголовкам узлов, чтобы строки заголовков были длиннее 5 символов. Если это все еще на месте, и мы запускаем код выше, проверка, очевидно, должна завершиться неудачей. Тем не менее, объект $violations
теперь является экземпляром EntityConstraintViolationListInterface
который предоставляет некоторые вспомогательные методы для доступа к данным нарушения, относящимся к объектам контента Drupal. Стоит изучить этот интерфейс для всех доступных вспомогательных методов.
Чтобы получить список нарушений на уровне сущностей, мы можем использовать метод getEntityViolations()
но мы также можем просмотреть все из них. Как только у нас есть отдельные экземпляры ConstraintViolationInterface
, мы можем проверить их на предмет ошибок. Например, мы можем получить сообщение об ошибке с помощью getMessage()
, путь свойства, который потерпел неудачу с помощью getPropertyPath()
и недопустимое значение с помощью getInvalidValue()
, среди других полезных вещей.
Когда дело доходит до полей, путь свойства имеет следующий формат: title.0.value
. Это включает в себя имя поля, ключ (дельта) отдельного элемента поля в списке и фактическое имя свойства. Это представляет путь собственности нашего нарушения выше.
Помимо вызова проверки для всей сущности (которая иногда может быть излишней), мы также можем сделать это непосредственно для каждого поля:
$entity - > set ( 'title' , 'this is too long of a title' ) ; $violations = $entity - > get ( 'title' ) - > validate ( ) ;
В этом случае $violations
снова является экземпляром ConstraintViolationListInterface
и может быть зациклен для проверки каждого нарушения. На этот раз, однако, путь свойства изменится и больше не будет включать имя поля: 0.value
.
И наконец, мы можем даже проверить отдельные элементы в списке:
$violations = $entity - > get ( 'title' ) - > get ( 0 ) - > validate ( ) ;
Как мы можем ожидать, теперь разница в том, что путь свойства будет показывать только value
нарушения, поскольку мы точно знаем, что мы проверяем: первое определение данных в списке.
Ограничения и валидаторы
Мы только коснулись аспекта ограничений и валидаторов, но давайте лучше поймем, как они работают, создав его сами. Мы создадим одно ограничение и валидатор, но это можно будет использовать как для сущностей узлов, так и для их полей. Всегда лучше иметь ограничения, нацеленные на определение данных, которое мы хотим, но ради краткости мы сделаем все это в одном ограничении, чтобы увидеть, как все эти параметры могут быть обработаны.
Бизнес-пример нашего примера состоит в том, чтобы иметь ограничение, которое мы можем применить к любому основному строковому объектному полю, которое заставит строку содержать определенный буквенно-цифровой код. Последний будет передан в качестве опции, поэтому он может быть использован повторно. Если он применяется к сущности Node, мы хотим убедиться, что заголовок узла содержит код. Итак, начнем.
Во-первых, внутри нашего demo
модуля, который можно найти в этом git-репозитории , нам нужно создать папку Validation
внутри папки Plugin
нашего пространства имен (каталог src/
). Внутри этого нам нужна папка Constraint
. Это потому, что ограничения — это плагины, которые, как ожидается, будут там определены.
Во-вторых, внутри Constraint/
мы можем создать наш класс ограничений:
<?php /** * @file * Contains \Drupal\demo\Plugin\Validation\Constraint\HasCodeConstraint. */ namespace Drupal \ demo \ Plugin \ Validation \ Constraint ; use Symfony \ Component \ Validator \ Constraint ; use Symfony \ Component \ Validator \ Exception \ MissingOptionsException ; /** * Constraint for checking if a string contains a certain alphanumerical code. * * @Constraint( * id = "HasCode", * label = @Translation("Has code", context = "Validation"), * type = { "string", "entity:node" } * ) */ class HasCodeConstraint extends Constraint { /** * Message shown when the code is missing. * * @var string */ public $messageNoCode = 'The string < em > % string </ em > does not contain the necessary code : % code . ' ; /** * The code this constraint is checking for. * * @var string */ public $code ; /** * Constructs a HasCodeConstraint instance. * * @param null $options */ public function __construct ( $options = NULL ) { if ( $options ! == NULL && is_string ( $options ) ) { parent : : __construct ( [ 'code' = > $options ] ) ; } if ( $options ! == NULL && is_array ( $options ) && isset ( $options [ 'code' ] ) ) { parent : : __construct ( $options ) ; } if ( $this - > code === NULL ) { throw new MissingOptionsException ( 'The code option is required' , __CLASS__ ) ; } } }
Как мы упоминали ранее, класс подключаемого модуля ограничений относительно прост. Он содержит ожидаемую аннотацию, которая среди стандартных метаданных также указывает, к какому типу данных может быть применено это ограничение. Мы выбрали как простую строку, так и тип сущности Node. Затем мы объявляем два открытых свойства: сообщение, которое будет использоваться в случае сбоя ограничения (с заполнителями, заполняемыми внутри валидатора) и фактический код, который будет проверяться (заполняется конструктором родительского класса Constraint
). Внутри нашего конструктора мы проверяем, являются ли переданные параметры строкой или массивом (просто чтобы быть немного гибкими), и генерируем исключение, если параметр кода не передается как параметр в какой-либо форме или форме.
Одна из задач родительского класса Constraint
— указать, какой класс будет использоваться для проверки этого ограничения. По умолчанию это класс, названный так же, как и само ограничение, но с добавленным в конце HasCodeConstraintValidator
( HasCodeConstraintValidator
). Если бы мы хотели создать класс с другим именем и / или пространством имен, нам пришлось бы переопределить метод validatedBy()
и вернуть полностью определенное имя класса, который мы хотим.
Давайте теперь посмотрим класс HasCodeConstraintValidator
поскольку для нас по умолчанию хорошо:
<?php /** * @file * Contains \Drupal\demo\Plugin\Validation\Constraint\HasCodeConstraintValidator. */ namespace Drupal \ demo \ Plugin \ Validation \ Constraint ; use Drupal \ Core \ Field \ FieldItemListInterface ; use Drupal \ node \ NodeInterface ; use Symfony \ Component \ Validator \ Constraint ; use Symfony \ Component \ Validator \ ConstraintValidator ; use Symfony \ Component \ Validator \ Exception \ UnexpectedTypeException ; use Symfony \ Component \ Validator \ Violation \ ConstraintViolationBuilderInterface ; /** * Validates the HasCodeConstraint. */ class HasCodeConstraintValidator extends ConstraintValidator { /** * Validator 2.5 and upwards compatible execution context. * * @var \Symfony\Component\Validator\Context\ExecutionContextInterface */ protected $context ; /** * {@inheritdoc} */ public function validate ( $data , Constraint $constraint ) { if ( ! $constraint instanceof HasCodeConstraint ) { throw new UnexpectedTypeException ( $constraint , __NAMESPACE__ . '\HasCodeConstraint' ) ; } // Node entity if ( $data instanceof NodeInterface ) { $this - > validateNodeEntity ( $data , $constraint ) ; return ; } // FieldItemList if ( $data instanceof FieldItemListInterface ) { foreach ( $data as $key = > $item ) { $violation = $this - > validateString ( $item - > value , $constraint ) ; if ( $violation instanceof ConstraintViolationBuilderInterface ) { $violation - > atPath ( 'title' ) - > addViolation ( ) ; } } return ; } // Primitive data if ( is_string ( $data ) ) { $violation = $this - > validateString ( $data , $constraint ) ; if ( $violation instanceof ConstraintViolationBuilderInterface ) { $violation - > addViolation ( ) ; return ; } } } /** * Handles validation of the title field of the Node entity. * * @param NodeInterface $node * @param HasCodeConstraint $constraint */ protected function validateNodeEntity ( $node , $constraint ) { foreach ( $node - > title as $item ) { $violation = $this - > validateString ( $item - > value , $constraint ) ; if ( $violation instanceof ConstraintViolationBuilderInterface ) { $violation - > atPath ( 'title' ) - > addViolation ( ) ; } } } /** * Handles validation of a string * * @param $string * @param $constraint * * @return \Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface */ protected function validateString ( $string , $constraint ) { if ( strpos ( $string , $constraint - > code ) === FALSE ) { return $this - > context - > buildViolation ( $constraint - > messageNoCode , array ( '%string' = > $string , '%code' = > $constraint - > code ) ) ; } } }
Основная задача этого класса — реализовать метод validate()
и создать нарушения в текущем контексте выполнения, если переданные ему данные каким-то образом недопустимы. Последние могут быть более одного типа данных, в зависимости от того, к чему применяется ограничение.
В нашем примере мы также используем ограничение для сущностей, списков элементов полей и примитивных данных. Это просто чтобы сэкономить нам немного места. Но логика заключается в том, что если передается сущность Node, код проверяется в его заголовке, в то время как для элементов поля выполняется итерация для проверки значений полей. И, конечно, ограничение также может быть добавлено к отдельным элементам поля, и в этом случае переменная $data
будет значением определения данных.
Свойство $context
имеет удобный buildViolation()
который позволяет нам указать ряд вещей, связанных с тем, что на самом деле не удалось (путь, сообщение и т. Д.).
Поэтому, если мы хотим использовать это ограничение, мы можем применить то, что узнали ранее, и выполнить одну из следующих 3 вещей:
Внутри hook_entity_type_alter()
:
$node = $entity_types [ 'node' ] ; $node - > addConstraint ( 'HasCode' , [ 'code' = > 'UPK' ] ) ;
Это добавляет ограничение к сущностям узла, поэтому все заголовки узлов теперь должны содержать код UPK
.
Внутри hook_entity_base_field_info_alter()
или hook_entity_bundle_field_info_alter()
одна из следующих двух строк:
$title - > addConstraint ( 'HasCode' , [ 'code' = > 'UPK' ] ) ; $title - > addPropertyConstraints ( 'value' , [ 'HasCode' = > [ 'code' = > 'UPK' ] ] ) ;
Где $title
— это определение поля, а первый случай добавляет ограничение ко всему списку элементов, тогда как последний добавляет его прямо к отдельному элементу.
Эти три возможности охватывают три случая, которые валидатор ограничений обрабатывает в нашем примере. И это в значительной степени это. Не так уж сложно
Вывод
В этой статье мы продолжили наше погружение в API Entity Validation, рассматривая две вещи: проверку и обработку нарушений и создание пользовательских ограничений. Мы видели, что после применения к объектам или определениям данных полей, ограничения очень легко проверяются и проверяются на предмет нарушений. Этот API многое заимствует у Symfony и позволяет легко отделить валидацию от API формы.
Как мы уже видели, также легко расширить существующий пул доступных критериев проверки. Это делается с помощью плагинов-ограничителей, каждый из которых связан со своими собственными валидаторами и который может быть написан очень многократно. Очень интересно поиграть с ними и посмотреть, как все части взаимодействуют. И это также большой опыт обучения.