第24回: リクエストバリデーション設計 — 「不正なデータはAPIの入口で止める」

章: 第3章: RESTful API設計

バリデーションをコントローラに直書きしていませんか?

if (empty($request->title)) { return error(); } をコントローラに書き続けると、バリデーションロジックが散らばり、テストも困難になります。複数のエンドポイントで同じルールが重複し、変更のたびに複数箇所を修正しなければなりません。

LaravelのForm Requestクラスを使えば、バリデーションロジックを専用クラスに集約し、コントローラはバリデーション済みのデータだけを受け取れます

リクエストバリデーションの実装


<?php
class StorePostRequest extends FormRequest {
    public function authorize(): bool { return true; }
    public function rules(): array {
        return [
            'title'  => ['required', 'string', 'max:255'],
            'body'   => ['required', 'string'],
            'tags'   => ['array'],
            'tags.*' => ['string', 'max:30'],
        ];
    }
    public function messages(): array {
        return ['title.required' => 'タイトルは必須です'];
    }
}

コントローラ直書き vs Form Request

観点 コントローラ直書き Form Requestクラス
責務の分離 コントローラが肥大化 バリデーションが専用クラスに
再利用 同じルールを複数箇所に書く クラスを使い回せる
テスト コントローラごとにテスト Form Requestを単体テスト
エラー応答 手動でエラーレスポンスを組む 422を自動で返す

チェックポイント: authorize() はリクエストの認可チェックです。return true だと全員許可ですが、実際は $this->user()->can('create', Post::class) のようにポリシーと連携させましょう。

コントローラでの使い方


<?php
class PostController extends Controller {
    // Form Requestをタイプヒントするだけでバリデーションが自動実行される
    public function store(StorePostRequest $request): JsonResponse {
        // ここに来た時点でバリデーション済み
        $post = Post::create($request->validated());
        return response()->json(['success' => true, 'data' => $post], 201);
    }
}

チェックポイント: $request->validated() はバリデーション済みのフィールドのみを返します。$request->all() を使うと未バリデーションのデータが混入するリスクがあります。

まとめ & 次のステップ

  • Form Requestはバリデーションロジックを専用クラスに分離する
  • コントローラは $request->validated() でバリデーション済みデータだけを受け取る
  • authorize() でポリシーとの連携が可能

次回は認証付きAPI(APIトークン)を学びます。LaravelのSanctumを使ってトークンベースのAPI認証を実装する方法です。

Related Articles