配列の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ではないなど)ニーズがあるかもしれませんね。
