CakePHP5でカスタムバリデーションに値を渡したいとき

CakePHP2よりも使い勝手はよくなったように思う
前回CakePHP5のバリデーションの基本的な使い方を紹介しました。
CakePHP2から5に乗り換えた話 〜バリデーション〜

これだけでも十分にエラーチェックはできるんですが、もしかしたら自作の関数にフォームの入力データとは別の値を渡したいときがあるかもしれません。僕は実際にあった。

そういうときはどうするかなんですが、主に二種類のやり方があります。



カスタムバリデーションファイル

これは前回も書いたものなんですが、二種類のやり方を見る前に自作のバリデーションファイルについてざっくり見ておきましょう。

例えばCustomValidator.phpという自作のバリデーション用ファイルをsrc/Model/Validationフォルダの下に作成する場合はこうなります。

// CustomValidator.php
namespace App\Model\Validation;
use Cake\Validation\Validator;

class CustomValidator extends Validator {
  // NGワードのエラーチェック
  public static function ngWord($value, $words, $context) {
    if(!is_array($word)) {
      $words = [$words];
    }

    return !in_array($value, $words);
  }
}

第一引数($value)・・・入力されたデータが自動的に入ります。
第二引数($words)・・・バリデーションルールを設定する際に値を渡すための変数です。
第三引数($context)・・・テーブルに関する情報などが自動的に入ります。



ruleを配列にする

CustomValidatorをテーブルのvalidationDefaultで使いたいときはsetProviderという関数を使ってこのクラスを読み込みます。

// UsersTable.php
use Cake\Validation\Validator;

class UsersTable extends AppTable {
  public function validationCustom(Validator $validator): validator {
    // カスタムクラスの読み込み
    $validator->setProvider('custom', 'App\Model\Validation\CustomValidator');

    $validator->notEmptyString('name', '名前が未入力です');
    $validator->add('name', 'ngWord', [
      'rule' => ['ngWord', ['test', 'sample']],
      'provider' => 'custom',
      'message' => '名前に使用できない単語です',
    ]);

    return $validator;
  }
}

このときruleのところを配列にして[関数名, 値]のようにすると関数の第二引数に値を渡すことができます。先ほどの$wordsのところですね。上記の場合は[‘test’, ‘sample’]という配列を渡しています。

ただしこの書き方は値を渡すことが前提なので、値を渡さないとエラーになってしまうことがあります。

// UsersTable.php
use Cake\Validation\Validator;

class UsersTable extends AppTable {
  public function validationCustom(Validator $validator): validator {
    // カスタムクラスの読み込み
    $validator->setProvider('custom', 'App\Model\Validation\CustomValidator');

    $validator->notEmptyString('name', '名前が未入力です');
    $validator->add('name', 'ngWord', [
      'rule' => 'ngWord', // これだとエラーになる
      'provider' => 'custom',
      'message' => '名前に使用できない単語です',
    ]);

    return $validator;
  }
}



$contextにデータを渡す

必ず値を渡す前提であれば問題ないのですが、状況に応じて値を渡したり渡さなかったりする場合は$contextに値を渡せばエラーを回避できます。

そのためにはまずngWord関数を少し書き換えます。

// CustomValidator.php
namespace App\Model\Validation;
use Cake\Validation\Validator;

class CustomValidator extends Validator {
  // NGワードのエラーチェック
  public static function ngWord($value, $context) {
    if(!isset($context['words'])) {
      return true;
    }

    if(!is_array($context['words'])) {
      $context['words'] = [$context['words']];
    }

    return !in_array($value, $context['words']);
  }
}

第二引数の$wordsを削除しました。これでルール作成時に何の値も渡さなくてもエラーになりません。第三引数を設定しない場合はテーブル情報などが自動的に第二引数の方に入ってくれるようになります。

あとはどうやって$contextに値を渡すかですが、この場合はruleの中に直接必要な処理を記述します。

// UsersTable.php
use Cake\Validation\Validator;

class UsersTable extends AppTable {
  public function validationCustom(Validator $validator): validator {
    // カスタムクラスの読み込み
    $validator->setProvider('custom', 'App\Model\Validation\CustomValidator');

    $validator->notEmptyString('name', '名前が未入力です');
    $validator->add('name', 'ngWord', [
      'rule' => function($value, $context) {
        $context['words'] = ['test', 'sample'];
        return CustomValidator::ngWord($value, $context);
      },
      'provider' => 'custom',
      'message' => '名前に使用できない単語です',
    ]);

    return $validator;
  }
}

これで$contextに値を追加した状態でngWord関数を使用できます。






$contextに値を入れる方はあまり使う機会はないかもしれないし、そもそも$contextの中身をいじるのはよろしくないことなのかどうかもよく分からないのですが、どうしてもエラーチェック時に値を渡す必要がある、でもフォームのhiddenなどを使いたくはない、みたいなときはこのやり方を検討するのもありだと思います。

その他のCakePHP5系の話はこちら
ようやくのぼりはじめたはてしなく遠い開発坂
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください