なずなログ

ただのSIer系SEが思ったことや色々書く感じのアレです

【Laravel】普通のRequestを受け取った後にFormRequestに切り替える

やりたいこと

汎用的なRequestで一旦受け取って、あとからFormRequestを作成するようにすることで、無駄にコントローラやメソッドを生やさなくて済むようにしたい。 例えばAPIなどで、サービスが複数存在しているときに各パラメータを若干カスタマイズしたいときなどに使える。

routes/api.php
Route::post('/{serviceName}/apply', 'APIController@somethingAction')->name('apply');

解決方法

APIController.php
    /** @var array サービス名とバリデーションに使用するFormRequestの関連付け */
    protected $formRequests = [
        'hoge' => 'App\Http\Requests\HogeRequest',
        'huge' => 'App\Http\Requests\HugaRequest'
    ];

    /**
     * なんかのアクション
     * 
     * @param  Illuminate\Http\Request $request
     * @param  string $serviceName
     */
    public function somethingAction(Request $request, string $serviceName)
    {
        if (empty($serviceName) || isset($this->formRequests[$serviceName]))
        {
            // サービスを特定できない場合はNot Foundエラー
            abort(404);
        }
        
        // バリデーション実行のため、FormRequestを作成する
        $formRequest = app()->make($this->formRequests[$serviceName]);

        // do something
    }

参考にしたやつ Use form request manually · Issue #7995 · laravel/framework

解説

FormRequestをDIコンテナ経由で生成すると、自動的にvalidateが走るから。

/Illuminate/Foundation/Providers/FormRequestServiceProvider.php

Illuminate\Foundation\Providers\FormRequestServiceProvider
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved) {
            $resolved->validateResolved();
        });
        $this->app->resolving(FormRequest::class, function ($request, $app) {
            $request = FormRequest::createFrom($app['request'], $request);
            $request->setContainer($app)->setRedirector($app->make(Redirector::class));
        });
    }

大まかな流れはこんな感じ。

  • $this->app->afterResolving(ValidatesWhenResolved::class, function ($resolved){});で依存解決後の挙動を定義
  • Illuminate\Foundation\Http\FormRequestIlluminate\Contracts\Validation\ValidatesWhenResolvedをインターフェースとして実装しているため、FormRequestServiceProviderで定義されたとおりにvalidateResolvedが呼び出される
  • Illuminate\Validation\ValidatesWhenResolvedTraitvalidateResolvedメソッドでバリデーションを実行

そのため、インスタンスを生成するだけでバリデーションが実行される。