第05回: Form Requestで複雑バリデーション — Controllerに検証コードを書くのはそろそろやめませんか?

章: 第2章: Web層の実践設計

$request->validate([...]) が100行を越えている、あるいは条件付きルールや配列バリデーションがヨリにたってくる――Controllerの記述が膨らんできたとき、Form Requestがその解決策です。

Form Requestは、入力検証をControllerから分離して専用クラスに委譲する 仕組みです。テストも書いていやすく、条件変更時の影響範囲も一目短然になります。

なぜForm Requestが重要なのか

入力検証をControllerに直書きすると次の問題が起きます。

  • $this->validate()$request->validate() が混在してコンシステンシーが崩れる
  • 条件付きルール(required_if, when)が混在して読むのが困難になる
  • 前処理(入力値の正規化)がどこに書けばいいか分からなくなる

チェックポイント: rules() は「検証ルール」のみ、prepareForValidation() は「検証前の入力変換」のみ。この分離を守るだけで大幅に整理されます。

Controllerに書く vs Form Requestに書く

観点 Controllerに直書き Form Request
責務の明確さ 低い(混在する) 高い(分離される)
テストの書きやすさ 低い 高い(第二引数に渡すだけ)
認可ロジックの場所 分散しがち authorize() に集約
条件付きルール 読みにくい sometimes()when()で整理

実装例


<?php
class StoreOrderRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'email' => ['required', 'email'],
            'items' => ['required', 'array', 'min:1'],
            'items.*.sku' => ['required', 'string'],
            'coupon' => ['nullable', 'string', 'exists:coupons,code'],
        ];
    }

    protected function prepareForValidation(): void
    {
        $this->merge(['email' => strtolower((string) $this->email)]);
    }
}

authorize() で認可ロジックも集約する


<?php
public function authorize(): bool
{
    return $this->user()->can('create', Order::class);
}

authorize()false を返すと自動的に403レスポンスとなり、Controllerでの漏れを防げます。

まとめ & 次のステップ

  • Form Requestで検証ロジックをControllerから分離し、単一責務原則を守る
  • prepareForValidation() で入力値の前処理(trim、小文字変換など)を集約する
  • authorize() で認可チェックもControllerから分離する
  • validated() で取得することで、検証済みのデータのみを使わせる安全性が上がる

次回は 第06回: API Resourceとレスポンス統一 で、レスポンス形式をコントローラから分離する方法を学びます。

Related Articles