В Drupal 8 валидация сущностей представляет собой отдельное API, которое отделено от механизма валидации форм. Оно основывается на компоненте Symfony Validator и связано с Typed Data API. поскольку сущности и поля являются типизированными данными. Это также означает, что можно добавлять валидацию к любым типизированным данным.
Так как валидация сущностей не привязана к формам (хотя она там тоже используется), её можно использовать по требованию. К примеру, это полезно при реализации веб-сервиса, что отражено в модулях JSON:API и RESTful Web Services.
Использование
Компонент Symfony Validator построен на концепции ограничений (constraints) и валидаторов. Первые определяют правила для валидации, а вторые описывают её логику. В Drupal 8 ограничения интегрированы в систему плагинов, то есть являются плагинами. Все ограничения наследуют класс Symfony\Component\Validator\Constraint
, а все валидаторы - Symfony\Component\Validator\ConstraintValidator
. Помимо ограничений из Symfony, в Drupal 8 описаны собственные ограничения. Например, уникальность значения поля, соответствие сущности типу, корректность формата даты и т.д.
Чтобы выполнить валидацию объекта типизированных данных, нужно вызвать метод validate()
. Результатом выполнения метода будет список нарушений, выявленных при проверке. Если список пуст, значит валидация прошла успешна. При этом некоторые типы данных сразу описывают, какие ограничения на них налагаются. К примеру, тип данных Email сразу связан с одноимённым ограничением.
В данном примере валидация пройдёт успешно, так как мы указали валидный email:
$definition = DataDefinition::create('email'); $typed_data = \Drupal::typedDataManager()->create($definition, 'foo-bar@example.com'); $violations = $typed_data->validate();
Можно добавлять свои ограничения:
$definition = DataDefinition::create('email'); $definition->addConstraint('Length', ['max' => 100]); $typed_data = \Drupal::typedDataManager()->create($definition, 'foo-bar@example.com'); $violations = $typed_data->validate();
Альтернативный способ выполнения валидации:
$violations = \Drupal::typedDataManager()->getValidator()->validate($typed_data);
Валидация отдельной сущности:
$violations = $entity->validate();
Валидация отдельного поля:
$violations = $entity->get('foo_bar')->validate();
Добавляем ограничение для поля (когда есть доступ к классу сущности):
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['foo_bar'] = BaseFieldDefinition::create('string') ->setLabel(t('Foo bar')) ->addConstraint('CustomFieldConstraint'); return $fields; }
Добавляем ограничение для поля (когда нет доступа к классу сущности):
/** * Implements hook_entity_bundle_field_info_alter(). */ function MODULE_NAME_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) { if ($entity_type->id() == 'foo' && $bundle == 'bar') { if (isset($fields['foo_bar'])) { $fields['foo_bar']->addConstraint('CustomFieldConstraint'); } } }
Ограничения можно указывать в аннотации сущности. В этом случае они будут применяться на все сущности данного типа. Если доступа к классу сущности нет, используем хук:
/** * Implements hook_entity_type_alter(). */ function MODULE_NAME_entity_type_alter(array &$entity_types) { $entity_types['foo']->addConstraint('CustomEntityConstraint'); }
Собственный валидатор
На одном из проектов мне недавно понадобилось добавить валидацию для поля с датой на форме ноды. Нужно было запретить ввод прошедшей даты. Для решения задачи я написал собственный валидатор. Его и рассмотрим в качестве примера.
Сначала определим ограничение (файл src/Plugin/Validation/Constraint/FutureDateConstraint.php
):
<?php namespace Drupal\MODULE_NAME\Plugin\Validation\Constraint; use Symfony\Component\Validator\Constraint; /** * Custom validation constraint. * * @Constraint( * id = "FutureDate", * label = @Translation("Future date", context = "Validation"), * ) */ class FutureDateConstraint extends Constraint { /** * The default violation message. * * @var string */ public $message = '@name field must be a future date.'; }
Далее определяем валидатор (файл src/Plugin/Validation/Constraint/FutureDateConstraintValidator.php
). Имя класса должно следовать шаблону ${ConstraintClassName}Validator
. Если имеется необходимость использовать другое имя, то оно указывается в классе ограничения в методе validatedBy()
.
<?php namespace Drupal\MODULE_NAME\Plugin\Validation\Constraint; use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\Constraint; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; /** * Custom constraint validator. */ class FutureDateConstraintValidator extends ConstraintValidator { /** * {@inheritdoc} */ public function validate($items, Constraint $constraint) { /** @var \Drupal\datetime\Plugin\Field\FieldType\DateTimeFieldItemList $items */ if (!isset($items) || $items->isEmpty()) { return; } /** @var \Drupal\datetime\Plugin\Field\FieldType\DateTimeItem $item */ foreach ($items as $item) { $value = $item->getValue()['value']; if (is_string($value)) { $date1 = new DrupalDateTime('now', DateTimeItemInterface::STORAGE_TIMEZONE); $date2 = new DrupalDateTime($value, DateTimeItemInterface::STORAGE_TIMEZONE); if ($date2->getTimestamp() < $date1->getTimestamp()) { $this->context->addViolation($constraint->message, [ '@name' => $items->getFieldDefinition()->getLabel(), ]); } } } } }
Наконец, добавим ограничение к полю (файл MODULE_NAME.module
):
/** * Implements hook_entity_bundle_field_info_alter(). */ function MODULE_NAME_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) { if ($entity_type->id() == 'node' && $bundle == 'foo') { if (isset($fields['field_foo_date'])) { $fields['field_foo_date']->addConstraint('FutureDate'); } } }
Добавить комментарий