Laravelのフォームリクエストのジレンマ
Laravelにはフォームリクエストという便利な機能がある。コントローラに適切なフォームリクエストをインジェクションさせると、リクエストがフォームリクエスト内のルールでバリデーションされた上でインジェクションされる。
<?php namespace App\Http\Requests; class UserRequest extends \Illuminate\Foundation\Http\FormRequest { public function rules() { return [ 'name' => ['string'], 'email' => ['email'], ]; } }
<?php namespace App\Http\Controllers; class UserController extends Controller { public function store(\App\Http\Requests\UserRequest $request) { dd($request->validated()); # バリデーション済みのデータが取得できる } }
上記のコードは、コントローラ側で以下のように実装しても同等だ。
<?php namespace App\Http\Controllers; class UserControllerWithValidation extends Controller { public function store(\Illuminate\Http\Request $request) { dd($request->validate([ 'name' => ['string'], 'email' => ['email'], ])); } }
フォームリクエストを使わない方がコードは短くなる。ではなぜフォームリクエストを使うのか? それはフォームリクエストを使うことでコントローラとバリデーションの双方向の依存を、コントローラからバリデーションだけの一方向の依存にできるからだ。
ではすべてのバリデーションをフォームリクエストで行おう、となりそうだが、じつはフォームリクエストは以下のようなルールで破綻する。
<?php namespace App\Http\Controllers; class UserControllerWithValidation extends Controller { public function update(\Illuminate\Http\Request $request, \App\User $user) { dd($request->validate([ 'name' => ['string'], 'email' => ['email', \Illumination\Validation\Rule::unique('users')->ignore($user->id)], ])); } }
バリデーションルールがルートと関連したモデルに依存してしまっている。このような、よくある、ちょっと複雑な例にフォームリクエストを使うことは意味がない。
フォームリクエストの利点はコントローラに依存しなくなることだが、ユーザモデルのインスタンスを取得するためにはコントローラに依存するか、もっと酷いことにルートに直接依存することになる。
フォームリクエストは Illuminate\Http\Requests
を拡張していて、同様に route
メソッド経由でルートにアクセスでき、そこからユーザモデルのインスタンスを取得できるが、コントローラ側でDIで間接的にルートから取得する方がまだマシだ。
ということで、フォームリクエストはコントローラへの依存を切り離せるので便利だが、依存を切り離すとコントローラに依存しないと用意できない複雑なルールに対応できなくなるので、じっさいにはほとんど使い所はない。
<?php namespace App\Http\Requests; class UserRequest extends \Illuminate\Foundation\Http\FormRequest { public function rules() { $user = $this->route()->parameter('user'); # 持って来ること自体はできるのだが return [ 'name' => ['string'], 'email' => ['email', \Illumination\Validation\Rule::unique('users')->ignore($user->id)], ]; } }
バリデーションルールは、素直にコントローラ内なりに書くのがよさそうだ。