Drupalistのみなさんこんにちは、スタジオ・ウミのエンティティ芸人こと新田です。今回はエンティティやエンティティに登録されているフィールドにカスタムのバリデーションを追加する方法を紹介します。

エンティティバリデーションとは

エンティティバリデーションとは以下のコードで実行されるバリデーションを指します。

$entity->validate();
$entity->field_name->validate();

フォームのバリデーションハンドラー等と違い、エンティティそのものに紐付いているため、

  • タイミングを問わず実行できる(フォーム、hook等好きなタイミングで上記のコードを追加するだけで実行できる。ちなみにエンティティフォームを保存するときはデフォルトで自動的に実行される)
  • バリデーションの処理が分散しにくいので、コードを管理しやすくなる

という特徴があり、とても便利です。個人的には特定の経路だけで実行したいと最初から決まっているバリデーション以外は全部エンティティバリデーションにするのがいいのではないかなと思います。

やり方

今回は例として、電話番号フィールド(field_telephone)と携帯番号フィールド(field_mobile_phone)のどちらかに値が入っていなければエラーになるようなバリデーションを作成し、ユーザーに適用してみます。

Entity Validation API Overview

Constraint(制約)を作る

まず、モジュールの/src/Plugin/Validation/Constraint下にSymfony\Component\Validator\Constraintを継承したクラスを作ります。このクラスではIDとエラーメッセージだけを定義します。

<?php

namespace Drupal\my_module\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
 * 電話番号か携帯番号のどちらかが入力されていなければいけない.
 *
 * @Constraint(
 *   id = "TelephoneAndMobilePhoneRequirement",
 *   label = @Translation("電話番号か携帯番号の入力", context = "Validation"),
 *   type = "string"
 * )
 */
class TelephoneAndMobilePhoneRequirement extends Constraint {

  public $missingTelephoneAndMobilePhone = '電話番号か携帯番号のどちらかを入力してください。';

}

Validatorを作る

このConstraintと対応するValidatorを作ります。場所は同じディレクトリで、Symfony\Component\Validator\ConstraintValidatorを継承し、クラス名は必ず[Constraint名]Validatorにする必要があります。こちらのクラスでは$entity->validate()メソッドが走ったときに実際に行うバリデーションを定義します。

<?php

namespace Drupal\my_module\Plugin\Validation\Constraint;

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

/**
 * 電話番号もしくは携帯番号の入力のバリデーションを行う.
 */
class TelephoneAndMobilePhoneRequirementValidator extends ConstraintValidator {

  /**
   * {@inheritdoc}
   */
  public function validate($user, Constraint $constraint) {
    $telephone = $user->get('field_telephone')->getString();
    $mobile_phone = $user->get('field_mobile_phone')->getString();

    // 電話番号か携帯番号のどちらも空の場合はエラー.
    if (empty($telephone) && empty($mobile_phone)) {
      $this->context->addViolation($constraint->missingTelephoneAndMobilePhone);
    }
  }

}

Constraintをエンティティに適用する

最後にConstraintをエンティティタイプやフィールドに適用します。これでエンティティやフィールドのvalidate()実行時にConstraintに対応するValidatorのvalidate()メソッドが実行されるようになります。

フィールドに適用する場合やカスタムエンティティタイプに適用する場合は方法が違うので詳しくはこちらからご覧ください。
Providing a custom validation constraint

/**
 * Implements hook_entity_type_alter().
 */
function my_module_entity_type_alter(array &$entity_types) {
  // ユーザーに電話番号と携帯番号の制約を適用する.
  $entity_types['user']->addConstraint('TelephoneAndMobilePhoneRequirement');
}

以上で設定は完了です。

ちなみに、フィールド単体にだけバリデーションを適用する場合はField Validationのプラグインを書く方が簡単で、管理画面からも確認できるのでおすすめです。

バリデーションの実行

エンティティフォームの場合

保存時に自動的に実行され、画面上にエラーメッセージが表示されます。 エンティティフォーム

バックエンドで実行する場合

バックエンドで実行する場合は以下のようにエラーメッセージ等を取り出すことができます。

$user->set('field_telephone', NULL);
$user->set('field_mobile_phone', NULL);

$violations = $user->validate();

foreach ($violations as $violation) {
  $message = $violation->getMessage()->__toString(); // "電話番号か携帯番号のどちらかを入力してください。"
}

というわけで、カスタムのエンティティバリデーションの追加方法でした。


共に働く新しい仲間を
募集しています

スタジオ・ウミは「Drupal」に特化したサービスを提供する Drupal のエキスパートチーム。
フルリモート&フレックス制だから、働く場所を選ばず時間の使い方も自由です。
そんなワークライフバランスの整った環境で、当ブログに書かれているような
様々な技術を共に学びながら、Drupalサイト開発に携わってみたい方を募集しています。
まずはお話だけでも大歓迎!ぜひお気軽にご連絡ください。