Laravelのバリデーションで繰り返しフィールドを扱う

フォームの中にたとえば「複数の商品を持つ」ような繰り返し領域があるケースは案外多いと思います。今回はそういった場合にどのようにバリデーションを設定するのか調べました。ちなみにバリデーションの実行の仕方はいくつかありますが、個人的にはLaravel5から追加されたFormRequestを積極的に利用してます。出てくるサンプルは全てFormRequestで設定しています。

まずHTML(blade)側はどのように記述するか

同じ名前で複数入ってくるものなのでinputのname属性は配列の形式で記述します。文字列[数字]という形式です。

例)item_name[0]

対して、エラーメッセージが入っている$errorsへのアクセスは文字列.数字という形式になります。

例)item_name.0

<div class="form-group @if($errors->has("item_name.{$idx}")) has-error @endif">
	<div class="row">
		<div class="col-sm-12">
			<input type="text" class="form-control" name="item_name[$idx]" value="{{ old('item_name')[$idx] ?? '' }}" />
			<div class="row has-error">
				<div class="col-xs-12">
					<span class="help-block">{{ $errors->first("item_name.{$idx}") }}</span>
				</div>
			</div>
		</div>
	</div>
</div>	

上記はbladeだけ想定して書きましたが動的にJavaScriptで差し込む場合もinputは同様のnameになるように中の数字を繰り上げてたとえばitem_name[2]という形で追加します。(Mustache等のテンプレートエンジンを使うと簡単です)

バリデーションの設定

次にFormRequest側でどのように記述するのか書きます。あまり難しくなくて、$this->getで取得すると上記のようなHTMLで指定したものが配列で取得できるので、配列をループして例えばitem_name.{$key}という形でルールを設定していけば良いです。

    public function rules()
    {
        $itemRules = [];
        $keys = array_keys($this->get('item_name') ?? []);
        foreach ($keys as $key) {
            $itemRules["item_name.{$key}"] = 'required|max:100';
        }
        return $itemRules;
    }

    public function attributes()
    {
        $itemAttributes = [];
        $keys = array_keys($this->get('item_name') ?? []);
        foreach ($keys as $key) {
            $itemAttributes["item_name.{$key}"] = "商品名[$key]";
        }
        return $itemAttributes;
    }

attributesメソッドでも同様です。こちらは設定するとバリデーションのエラーメッセージの:attributeを任意の文字列に置き換える事ができます。

バリデーションの設定の仕方としてはこんな感じです。

FormRequestの利点

FormRequestでは上記のようにrulesを設定するのに使えるのですが、今見たようにフォームの構造に密接なのでこれを利用して、ぼくの場合はビジネスロジックに渡すパラメーターを生成するのに利用しています。

たとえばこんな感じです。

    private function getItemsParams()
    {
        $items = [];
        $keys = array_keys($this->get('item_name') ?? []);
        foreach ($keys as $key) {
            $item = [
                'item_name' => $this->get('item_name')[$key] ?? '',
            ];
            $items[] = $item;
        }
        return $items;
    }

こうしておくと、ControllerでFormのパラメーターをコネコネしなくてもRequestから取り出すだけなので簡潔ですしController側ではFormのパラメーターを知らなくても良いという利点もあります。