配列のDistinctバリデーション
前回の例題とした商品オプションに、オプション名に同じ値が入ることを禁じるバリデーションを追加します。
これには、配列プレースホルダー .*
ともに laravel 5.2 で追加された distinct
バリデーションが利用できます。よく考えられていますね。
$rules = [ 'option_id' => 'required', 'option_name.*' => 'required_with:option_id.*|distinct', 'unit_price.*' => 'required_with:option_id.*|integer|min:0', 'inventory.*' => 'required_with:option_id.*|integer|min:0', ];
空の入力に対するdistinct?
ところで、ここまで「オプション名」は必須入力であることを前提としてきましたが、商品オプションが1つだけなら「オプション名」は空のまま「販売単価」と「在庫数」だけ入力できるようにしたい、という要望が現場から上がってくるかもしれません。
もう少し仕様追求すると、商品オプションが2つ以上であっても、先頭(とは限らないかもしれません)の1つは空でよいかもしれません。
このために空の入力が1個まで可というバリデーションルールを追加するとロジックが複雑化します。required_with
を外したとき distinct
が空文字列もあわせて重複チェックしてくれれば話が簡単ですね。こんなふうに・・・
3つの入力欄のうち2つを空にした場合のリクエストは次のようになります。
Array ( [option_name] => Array ( [11] => [12] => [13] => オレンジ )
option_name
のキー 11 と 12 は同じ値(空文字列)なのだから重複チェックにかかりそうなものです。
しかしこのバリデーションはそんな都合よく機能しません。
distinct
バリデーションは、option_name
全体ではなく、配列の値それぞれに働きます。キー 11 の値は配列内で重複してるか? キー 12 の値は・・・と。
そして、バリデーションの原則は値が入力された項目にのみ働きます。このケースでは、そもそもキー 11 と 12 は調査されることなく、キー 13 の値が配列内で重複してるかだけが調べられ、重複なしと判定されるのです。
そう、リクエストが空でも働くのは、required
系バリデーションだけでした。
暗黙の拡張 implicitRules
ここまでくると次の展開が読めましたか?
この課題は解決には、distinct
が required
と同様に 空リクエストに対してもバリデーションが働く 必要があり、これを実現するのが 暗黙の拡張 ルールです。
この暗黙の拡張を「暗黙の必須」と読んでしまうと理解を間違えます。空リクエストに対するバリデーション、それ以上でも以下でもありません。
暗黙の拡張ルールを作るには、Validator::extendImplicit()
メソッドを使います。通常の拡張ルールとして登録されると同時に、暗黙の拡張ルールの名前を収めた配列 $implicitRules
にルール名が追加されます。
Validator::extendImplicit('foo', function($attribute, $value, $parameters, $validator) { return $value == 'foo'; });
ということは、Validator
を継承して作成した customValidaor
クラスでは、直接 $implicitRules
にルール名を追加することで暗黙の拡張を作ることができます。
以下は、空文字列を含んだ重複判定ができる distinct_with_blank
バリデーションの作成例です。
class CustomValidator extends \Illuminate\Validation\Validator { public function __construct($translator, $data, $rules, $messages = []) { parent::__construct($translator, $data, $rules, $messages); // 暗黙の拡張に追加 $this->implicitRules[] = 'DistinctWithBlank'; } /** * 空文字列も含む重複判定 distinct_with_blank * @param string $attribute * @param string $value * @param array $parameters * @return true */ public function validateDistinctWithBlank($attribute, $value, $parameters) { // 標準distinctを呼ぶだけ return parent::validateDistinct($attribute, $value, $parameters); }
これを用いて option_name.*
から required_with
を外すと、最初のルール定義は次のようになります。無事に、オプション名の空は1つだけ許可されるようになりました。
$rules = [ 'option_id' => 'required', 'option_name.*' => 'distinct_with_blank', 'unit_price.*' => 'required_with:option_id.*|integer|min:0', 'inventory.*' => 'required_with:option_id.*|integer|min:0', ];
この他にも、DB上の重複をチェックする unique
に対し、空入力でも機能する unique_with_blank
も状況によっては(DB定義がNULL OKではないなど)ニーズがあるかもしれませんね。