laravel 5.2 で、バリデーションルールに配列を表す .*
というプレースホルダーが使えるようになりました。
例えば、次のような商品オプションのバリデーションを考えます。
入力行の追加UIやドラッグ&ドロップによるソートはjQueryなどで実装することにします(laravel から離れるので解説は省きます)
この場合、項目数がいくつになるかわからないので、エレメントの属性名を name="option_name[{{$id}}]"
などとし、配列を返すように作りますよね。
リクエストは次のようなものになるでしょう。配列のキーは編集データに依存しますので不定です(ここでは仮に 11, 12, 13 としました)
Array ( [option_id] => Array ( [11] => Y [12] => Y [13] => Y ) [option_name] => Array ( [11] => ホワイト [12] => ブラック [13] => オレンジ ) [unit_price] => Array ( [11] => 1000 [12] => 1200 [13] => 980 ) [inventory] => Array ( [11] => 30 [12] => 20 [13] => 15 ) )
最低必要なバリデーションとしては、「保存」が少なくとも1つチェックされることと、「保存」がチェックされた行は必須入力となること、そして「販売単価」と「在庫数」の数値判定です。
苦労していた配列のバリデーションですが、5.2 からは次のようなルール定義が可能になったのです。
$rules = [ 'option_id' => 'required', 'option_name.*' => 'required_with:option_id.*', 'unit_price.*' => 'required_with:option_id.*|integer|min:0', 'inventory.*' => 'required_with:option_id.*|integer|min:0', ]; $messages = [ 'option_id.required' => '保存するレコードを少なくとも1つ選択してください', ];
「オプション名」、「販売単価」、「在庫数」のルール定義には属性名に .*
を加えることで、配列の値それぞれにバリデーションが適用されるようになります。
「保存」がチェックされた行だけ入力が必要なので、required_with
の引数も option_id.*
となります。一方で「保存」の必須チェックは配列全体の required
です。
配列部分のエラーは array_dot()
による配列ドット記法で返ってきますので、Bladeテンプレートのエラーメッセージ表示は次のような書き方になります。
{{ $errors->first("unit_price.$id") }}
laravel 4 や 5.1 以前での配列バリデーション
後出しジャンケンでの自慢話となってしまいますが、私たちのプロジェクトでは lavavel 4 のころから配列に対するバリデーションを、5.2 と同じルールの書き方で処理してきました。
もともと laravel のバリデーションでは、リクエストは配列のまま処理されるでのはなく、array_dot
によるドット記法の一次元データに変換されて内部処理されていました。
例えば次のように、配列のキーが固定されてる入力フォームの場合ならば、
<input type="text" name="name[last]" value="{{ old('name.last') }}"> <input type="text" name="name[first]" value="{{ old('name.first') }}">
Array ( [name] => Array ( [last] => 山田 [first] => 太郎 ) )
以下のようにドット記法でルールを定義することで、配列データもバリデーションできたのです。
$rules = [ 'name.last' => 'required', 'name.first' => 'required', ];
最初に、「5.2 で .*
というプレースホルダーに対応した」と書き、配列バリデーションそのものに対応したと書かなかった意味がここにあります。
このプレースホルダーの変換を自分で対応すれば、5.1 以前や 4 でも同様の処理ができるわけです。
定義するルール:
$rules = [ 'option_id' => 'required', 'option_name.*' => 'required_with:option_id.*', 'unit_price.*' => 'required_with:option_id.*|integer|min:0', 'inventory.*' => 'required_with:option_id.*|integer|min:0', ];
変換後のルール:
$rules = [ 'option_id' => 'required', 'option_name.11' => 'required_with:option_id.11', 'option_name.12' => 'required_with:option_id.12', 'option_name.13' => 'required_with:option_id.13', 'unit_price.11' => 'required_with:option_id.11|integer|min:0', 'unit_price.12' => 'required_with:option_id.12|integer|min:0', 'unit_price.13' => 'required_with:option_id.13|integer|min:0', 'inventory.11' => 'required_with:option_id.11|integer|min:0', 'inventory.12' => 'required_with:option_id.12|integer|min:0', 'inventory.13' => 'required_with:option_id.13|integer|min:0', ];
この変換は次のような関数で対応できます。ベースコントローラかトレイトに入れて、バリデーション直前にルールの変換を挿入してみてください。
/** * 配列バリデーションルールの変換 * @param \Illuminate\Http\Request $request * @param array $rules * @param array $messages */ public static function setArrayRules($request, &$rules, &$messages) { foreach($rules as $field => $rule) { // field文字列は foo.* か? if (!preg_match('/^(.+)\.\\*$/', $field, $m)) continue; // fooはリクエストに存在して配列か? $name = $m[1]; if (!($req = $request->get($name)) || !is_array($req)) continue; foreach(array_keys($req) as $i) { // rulesに foo.$i を複写 $rules["$name.$i"] = str_replace('*', $i, $rule); // messagesにfoo.$i.barがあれば複写 foreach($messages as $key => $message) { if (!preg_match("/^$name\.\\*\.(.+)$/", $key, $m)) continue; $messages["$name.$i.{$m[1]}"] = $message; } } unset($rules[$field]); } }
p.s.
上で紹介した setArrayRules()
では、次のような配列の記述には対応してません。もし必要とするならばご自身で考えてみてくださいね。
Bladeソース:
<input type="text" name="option[{{$id}}][name]" value="old("opton.$id.name")">
バリデーションルール:
'option.*.name' => 'required_with:option_id.*',メルマガ購読の申し込みはこちらから。
[…] バリデーション (8) 配列をバリデーションする […]