フォームに入力された内容を新規マスタとして登録する機能を実装しようとしていました。
フォームに入力後、確認画面に行く前にチェックを行い、すでにデータベースに登録済みのデータならばエラー表示を出したかった。
保存時のsaveメソッドで行われるbuildRulesのバリデーションでも重複チェックはできますが、入力内容確認のバリデーション時に一緒に行いたいということもあり、TableクラスのvalidationDefaultで重複チェックをさせたかったのです。
しかし、CakePHP3のvalidationDefaultで重複チェックを行うための情報を、ググってもなかなか見つけることができなかったので時間がかかりました。
(CakePHP v3.4.4)
validationDefaultでの重複チェック記述法
「url」のカラムに対して重複チェックをしています。
【GroupsTable.php】
$validator
->add('url',[
'url' => [
'rule' => ['custom','/^[a-z0-9]+$/I'],
'message' => '半角英数字のみ使用できます'
],
'url_unique' => [
'rule' => 'validateUnique',
'provider' => 'table',
'message' => 'そのURLはすでに使用されています'
]
]);
…
…
…
return $validator;
この部分ですね。
↓ ↓ ↓
'url_unique' => [ //←ルール名(任意で好きな名前,でも他と名前が同じにならないように)
'rule' => 'validateUnique', //←ここです
'provider' => 'table', //←ここです
'message' => 'そのURLはすでに使用されています' //←エラー時のメッセージを設定
]
「rule」 に 「validateUnique」、
「provider」 に 「table」をセットするだけです。
結果のみお伝えすると、これで重複チェックが可能となります。
、、、、、、、?
「rule」に「validateUnique」をセットするだけじゃダメなの?
今まで使わなかった「provider」が、なぜここで出てきたの?
というような、私と同じモヤモヤをしている方が少なからずいると思いますので、その方のために続けてご説明いたします。
ちなみに、「ルール名は他と同じ名前にならないように」と書きましたが、それに関する詳細記事はこちらです。
テーブルプロバイダーのルールを使用する
今回特に肝なのは「’provider’ => ‘table’」の部分です。
そもそもバリデーションルールというものは「プロバイダー」からもたらされます。
特に「プロバイダー」の設定をしなければ、バリデーションルールはデフォルトでValidationクラスにマッピングされます。
Validationクラスの中身を少し見てみましょう。
【/vender/cakephp/cakephp/src/Validation/Validation.php】
・・・
・・・
・・・
/**
* Checks whether the length of a string (in characters) is greater or equal to a minimal length.
*
* @param string $check The string to test
* @param int $min The minimal string length
* @return bool Success
*/
public static function minLength($check, $min)
{
return mb_strlen($check) >= $min;
}
/**
* Checks whether the length of a string (in characters) is smaller or equal to a maximal length.
*
* @param string $check The string to test
* @param int $max The maximal string length
* @return bool Success
*/
public static function maxLength($check, $max)
{
return mb_strlen($check) <= $max;
}
・・・
・・・
・・・
上記には「minLength」と「maxLength」がありますが、他にもよく使う「notEmpty」や「date」など、たくさんの関数がこのValidationクラス詰め込まれています。
「プロバイダー」に何も設定しなければ、デフォルトでこのValidationクラスにマッピングするので、これらのコアバリデーションルールを記載するだけで使用できていましたね。
しかし、今回使用したかった「validateUnique」はValidationクラスではなく、Tableクラスに存在します。
Tableクラスの該当箇所を見てみましょう。
【/vender/cakephp/cakephp/src/ORM/Table.php】
・・・
・・・
・・・
public function validateUnique($value, array $options, array $context = null)
{
if ($context === null) {
$context = $options;
}
$entity = new Entity(
$context['data'],
[
'useSetters' => false,
'markNew' => $context['newRecord'],
'source' => $this->getRegistryAlias()
]
);
$fields = array_merge(
[$context['field']],
isset($options['scope']) ? (array)$options['scope'] : []
);
$values = $entity->extract($fields);
foreach ($values as $field) {
if ($field !== null && !is_scalar($field)) {
return false;
}
}
$rule = new IsUnique($fields, $options);
return $rule($entity, ['repository' => $this]);
}
・・・
・・・
・・・
ここに存在しましたね。
なので、テーブルプロバイダーのルールを使用するために、
今回は「'provider' => 'table'」の行を入れました。
バリデーションの確認
少し脱線しましたが、確認してみます。
すでに登録済みのurlを登録しようとした場合
【GroupsTable.php】
$group = $this->newEntity();
$data['url'] = 'news'; //←’news’はすでにDBに登録済み
$group = $this->patchEntity($group,$data);
$errors = $group->errors();
debug($errors);
↓ ↓ ↓
'url' => [
'url_unique' => 'そのURLはすでに使用されています。'
]
ちゃんとエラーとして抽出することができました。
あとは「$errors」をcontroller側に返して、処理を分岐したり表示させたりするだけ。
まとめ
validationDefaultでの重複チェックは
テーブルプロバイダーのルールを使用する
「rule」 に 「validateUnique」
「provider」 に 「table」をセットする
これまで、ただルールを記載しておまじないのようにバリデーションを使っていましたが、今回の件で少し理解を深められたかなと思います。
参考文献
CakePHP Cookbook 3.5 ドキュメント バリデーション
飲食業界からIT業界に転身してきて現在2年目です。PHPの経験がメインとなります。これまで自分がPHPを扱ってきた上で、モヤモヤしてきたことをメインに記事にしていきますのでよろしくお願いいたします。