章: 第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とレスポンス統一 で、レスポンス形式をコントローラから分離する方法を学びます。