Проверка данных является очень важной частью любого приложения. В Drupal 7 есть отличный API форм, который может выполнять сложную проверку представленных данных, которые затем можно превратить в сущности. Однако проверка на уровне формы проблематична. Например, становится трудно обрабатывать проверку сущности (данных) программно. Мы должны либо переопределить логику проверки, либо подражать представлениям формы в коде. Это делает любое взаимодействие данных зависимым от системы форм, и это плохая идея.
С введением таких подсистем, как REST API, Drupal 8 потребовалось что-то лучшее для решения этой проблемы. Был выбран компонент Symfony Validation, и на его основе строится Drupal 8, чтобы адаптировать его к реалиям системы сущностей на основе типизированных данных и плагинов. Итак, теперь отправка формы проверяет сущности так же, как это делают вызовы REST или любое другое программное взаимодействие с сущностями.
В этой статье и ее продолжении мы рассмотрим API проверки сущности Drupal 8, посмотрим, как он работает и как его можно расширить. Чтобы лучше понять это, мы также рассмотрим API Typed Data, который лежит в основе системы управления сущностями в Drupal 8. Будут некоторые примеры кода, которые уже существуют в ядре, но мы также напишем немного нашего собственного кода, который может быть находится в этом git-репозитории в demo
модуле.
Типизированные данные
Typed Data — это API-интерфейс Drupal 8, созданный для обеспечения согласованного способа взаимодействия с данными или метаданными о самих данных. Почему это важно для нашей темы? Потому что проверка определяется и вызывается для типизированных объектов данных.
DataType
два важных компонента этого API: определение данных и плагины DataType
. Роль первого — определить данные и то, как с ними работает взаимодействие (включая такие вещи, как настройки или ограничения проверки). Роль последнего заключается в предоставлении способа получения и установки значений из данных этого типа. Когда они создаются, плагины типов данных используют экземпляры определения данных, передаваемые менеджером плагинов. Последний также может определить, какое определение данных должно использоваться DataType
плагина DataType
.
Давайте посмотрим на пример:
$definition = DataDefinition : : create ( 'string' ) - > addConstraint ( 'Length' , array ( 'max' = > 20 ) ) ;
Мы создали определение string
данных и применили к нему ограничение Length
. Ограничения являются ключевой частью API проверки и определяют тип проверки, которая будет выполняться на данных. Они связаны с валидатором, который фактически выполняет задачу, но мы увидим больше об ограничениях и валидаторах во второй части этой серии.
Далее мы используем DataType
плагинов DataType
для создания фактического экземпляра плагина типа данных:
$string_typed_data = \ Drupal : : typedDataManager ( ) - > create ( $definition , 'my string' ) ;
Для краткости мы загрузили сервис менеджера статически, но вы должны использовать внедрение зависимостей в вашем проекте, если вы находитесь в контексте класса. Метод create()
в TypedDataManager
принимает определение данных в качестве первого параметра, фактическое значение — в качестве второго и возвращает DataType
подключаемого модуля DataType
того типа, который соответствует определению: в нашем случае StringData
. Для сложных данных плагины DataType
могут указывать в своих аннотациях, какой класс определения данных им нужно использовать. А для получения дополнительной информации о плагинах в Drupal 8 обязательно ознакомьтесь с моими предыдущими статьями на эту тему.
Одним из методов этого экземпляра плагина является validate()
, который запускает проверку данных по всем ограничениям, примененным к определению, и возвращает экземпляр ConstraintViolationList Symfony. Итерируя по нему или используя такие методы, как count()
или get()
мы можем проверить, действительны ли данные и какие ограничения не сработали, если нет.
В нашем примере в списке нарушений не должно быть ошибок проверки, потому что мы использовали строку длиной менее 20 символов. Если бы мы использовали более длинную строку при создании плагина, у нас было бы одно нарушение, представленное экземпляром Symfony ConstraintViolationInterface
с сообщением, ошибочным значением и даже путем свойства.
Типизированные объекты данных и содержимого
Теперь, когда мы немного знаем об API Typed Data, давайте посмотрим, как это относится к сущностям контента.
Данные сущностей в Drupal 7 разделены между свойствами сущностей (обычно столбцами в таблице сущностей) и полями API полей, которые настраиваются через пользовательский интерфейс. В Drupal 8 они попали под один и тот же зонт, поэтому старые свойства также становятся полями. Тем не менее, по-прежнему сохраняется различие в том, что некоторые поля (в основном свойства старых объектов) определяются как базовые поля, а остальные — настраиваемые поля.
Определения данных для этих полей — BaseFieldDefinition
и FieldConfig
, но оба они являются реализациями одного и того же FieldDefinitionInterface
(который является сложным расширителем DataDefinitionInterface
— интерфейса, непосредственно реализованного DataDefinition
мы видели ранее).
Каждое отдельное поле содержит данные в специальной реализации FieldItemListInterface
и всегда представляет собой список отдельных плагинов FieldItem
(даже если в поле есть только одно действительное значение). Каждый из этих плагинов расширяет плагин DataType
и использует тип DataDefinitionInterface
реализации DataDefinitionInterface
(обычно FieldItemDataDefinition
). На этом уровне все довольно сложно, поэтому стоит поближе взглянуть на структуру полей сущностей, чтобы лучше понять эту архитектуру.
Добавление ограничений сущности и поля
Ограничения в Drupal 8 — это также плагины, которые обычно содержат небольшой объем информации о том, как на самом деле проверяются данные, какое сообщение об ошибке следует использовать в случае сбоя и какие дополнительные параметры нужны валидатору. Класс валидатора (на который ссылается ограничение) отвечает за проверку данных. Мы видели один пример, Length
, который фактически является классом LengthConstraint
проверяемым непосредственно классом Symfony LengthValidator
. Во второй части мы увидим, как создать собственное ограничение и валидатор. Сейчас же давайте посмотрим, как мы можем добавить существующие ограничения к сущностям и полям контента.
Ограничения уровня объекта
Ограничения уровня объекта добавляются в аннотацию самого класса объекта. Например, вот как сущность Comment
определила ограничение для своей комбинации полей имя / автор:
. . . constraints = { "CommentName" = { } } . . .
В этом примере CommentName
является идентификатором плагина ограничения, которое будет проверяться при сохранении сущности комментария. Открывающая и закрывающая фигурные скобки означает, что плагин не принимает никаких опций.
Если бы мы хотели добавить или удалить ограничение из существующей сущности, нам нужно было бы реализовать hook_entity_type_alter()
:
function demo_entity_type_alter ( array & $entity_types ) { /** @var \Drupal\Core\Entity\ContentEntityType $node */ $node = $entity_types [ 'node' ] ; $node - > addConstraint ( 'ConstraintPluginName' , [ 'array' , 'of' , 'options' ] ) ; }
В этом примере мы добавляем фиктивное ограничение к сущности Node и передаем ему массив опций.
Ограничения на уровне поля
Существует несколько способов добавления ограничений на уровне поля в зависимости от того, определен ли тип объекта содержимого в нашем модуле и является ли тип поля, о котором мы говорим, базовым или настраиваемым.
Если мы определяем наш собственный тип сущности, один из методов реального класса сущностей, который мы должны реализовать, это baseFieldDefinitions()
. Здесь мы возвращаем массив определений полей BaseFieldDefinition
и мы можем легко добавить наши ограничения там. Например, вот как определяется базовое поле Node ID:
$fields [ 'nid' ] = BaseFieldDefinition : : create ( 'integer' ) - > setLabel ( t ( 'Node ID' ) ) - > setDescription ( t ( 'The node ID.' ) ) - > setReadOnly ( TRUE ) - > setSetting ( 'unsigned' , TRUE ) ;
Аналогично тому, как мы добавляли ограничения к экземпляру DataDefinition
ранее, сущность Node также может добавлять ограничения в поле Node ID, более конкретно:
- к
BaseFieldDefiniton
, который, как мы видели, является определением для реализацииFieldItemListInterface
(список) - к отдельным элементам
FieldItemDataDefinition
, которые, как мы видели, являются типом сложного определения данных для реализацийFieldItemInterface
(элементы)
Если мы хотим добавить ограничение в базовое поле узла (или любого другого типа сущности контента, не определенного нашим модулем), мы должны реализовать hook_entity_base_field_info_alter()
и добавить туда наше ограничение:
function demo_entity_base_field_info_alter ( & $fields , \ Drupal \ Core \ Entity \ EntityTypeInterface $entity_type ) { if ( $entity_type - > id ( ) === 'node' ) { /** @var \Drupal\Core\Field\BaseFieldDefinition $title */ $title = $fields [ 'title' ] ; $title - > addPropertyConstraints ( 'value' , [ 'Length' = > [ 'max' = > 5 ] ] ) ; } }
В приведенном выше примере мы добавляем ограничение в поле заголовка узла, чтобы убедиться, что заголовок не может быть длиннее 5 символов. addPropertyConstraint()
отметить, что мы используем метод addPropertyConstraint()
вместо метода addConstraint()
который мы видели ранее. Это связано с тем, что мы нацелены не на определение списка элементов, а на определения отдельных элементов ( FieldItemDataDefinition
).
Используя метод addConstraint()
, мы добавляем ограничение ко всему списку элементов. Это означает, что, когда ограничение проверяется, валидатор получает весь список, а не только значение отдельного элемента.
Если мы хотим добавить ограничение в настраиваемое поле, процесс будет довольно похожим. Разница в том, что нам нужно реализовать hook_entity_bundle_field_info_alter()
и работать с экземплярами FieldConfig
вместо BaseFieldDefinition
.
Если мы хотим проверить ограничения, которые уже установлены на поле, мы можем сделать что-то вроде этого:
$title = $fields [ 'title' ] ; $constraints = $title - > getConstraints ( ) ; $property_constraints = $title - > getItemDefinition ( ) - > getConstraints ( ) ;
В этом примере $title
является экземпляром BaseFieldDefinition
или FieldConfig
. Массив $constraints
, как и ожидалось, представляет собой список ограничений, применяемых ко всему списку элементов поля, тогда как $property_constraints
— это массив ограничений, применяемых к самим отдельным элементам поля. Тем не менее, мы можем заметить, что если мы запустим этот код после того, как применили ограничение Length
, то внутри $property_constraints
мы найдем ограничение ComplexData
которое охватывает отдельные ограничения, применяемые к значениям поля. Это потому, что может быть несколько отдельных определений данных для одного элемента поля, и Drupal по умолчанию группирует их следующим образом.
Вывод
В этой статье мы начали изучать API проверки сущностей в Drupal 8. Для этого нам также нужно было получить представление о API типизированных данных, который используется в качестве основы для системы сущностей. Как только это стало немного понятнее, мы увидели, как можно добавлять ограничения к различным типам определений данных, включая те, которые используются сущностями и полями.
В следующей части мы рассмотрим, как работает фактическая проверка и как следует обрабатывать возможные нарушения. Кроме того, мы создадим наше собственное ограничение и валидатор и применим наши знания из этой части к различным типам определения данных. Веселье!