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) 配列をバリデーションする […]