You are here
Home > !Laravel > バリデーション (8) 配列をバリデーションする

バリデーション (8) 配列をバリデーションする

laravel 5.2 で、バリデーションルールに配列を表す .* というプレースホルダーが使えるようになりました。

例えば、次のような商品オプションのバリデーションを考えます。

option_name

入力行の追加UIやドラッグ&ドロップによるソートはjQueryなどで実装することにします(laravel から離れるので解説は省きます)

この場合、項目数がいくつになるかわからないので、エレメントの属性名を name="option_name[{{$id}}]" などとし、配列を返すように作りますよね。

リクエストは次のようなものになるでしょう。配列のキーは編集データに依存しますので不定です(ここでは仮に 11, 12, 13 としました)

最低必要なバリデーションとしては、「保存」が少なくとも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.*',

Leave a Reply

Top