2024-06-17

LaravelのFormRequestの仕組み

LaravelのFormRequestの仕組みを調べてみたので備忘録。 Laravelのバージョンは11.0.7です。

ServiceProviderでのコールバック関数の設定

ServiceProviderでFormRequestのインスタンス化とバリデーションする処理の追加をしています。

Illuminate\Foundation\Providers\FoundationServiceProvider経由で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));
        });
    }

https://github.com/laravel/framework/blob/v11.0.7/src/Illuminate/Foundation/Providers/FormRequestServiceProvider.php#L27-L38

Container::resolving() では $resolvingCallbacks プロパティにコールバック関数がセットされます。

    /**
     * Register a new resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function resolving($abstract, ?Closure $callback = null)
    {
        if (is_string($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        if (is_null($callback) && $abstract instanceof Closure) {
            $this->globalResolvingCallbacks[] = $abstract;
        } else {
            $this->resolvingCallbacks[$abstract][] = $callback;
        }
    }

https://github.com/laravel/framework/blob/11.x/src/Illuminate/Container/Container.php#L1163-L1174

Container::afterResolving() では $afterResolvingCallbacks プロパティにコールバック関数がセットされます。

    /**
     * Register a new after resolving callback for all types.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function afterResolving($abstract, ?Closure $callback = null)
    {
        if (is_string($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        if ($abstract instanceof Closure && is_null($callback)) {
            $this->globalAfterResolvingCallbacks[] = $abstract;
        } else {
            $this->afterResolvingCallbacks[$abstract][] = $callback;
        }
    }

https://github.com/laravel/framework/blob/11.x/src/Illuminate/Container/Container.php#L1183-L1194

HTTPリクエスト時のインスタンス化

Container::make() すると Container::resolve() が呼び出されます。

    protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        // ...(省略)...

        $object = $this->isBuildable($concrete, $abstract)
            ? $this->build($concrete)
            : $this->make($concrete);

        // ...(省略)...
        
        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }

        // ...(省略)...

        return $object;
    }

https://github.com/laravel/framework/blob/11.x/src/Illuminate/Container/Container.php#L755-L816

fireResolvingCallbacks()resolvingCallbacks afterResolvingCallbacks にセットされたコールバックを呼び出します。

    protected function fireResolvingCallbacks($abstract, $object)
    {
        $this->fireCallbackArray($object, $this->globalResolvingCallbacks);

        $this->fireCallbackArray(
            $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
        );

        $this->fireAfterResolvingCallbacks($abstract, $object);
    }

https://github.com/laravel/framework/blob/11.x/src/Illuminate/Container/Container.php#L1236-L1245

resolvingCallbacksでインスタンス化したFormRequestにRequestクラスから値が設定されます。 https://github.com/laravel/framework/blob/11.x/src/Illuminate/Http/Request.php#L439-L472

FormRequestクラスはValidatesWhenResolvedインターフェースを実装しているので、ValidatesWhenResolved::classで設定した処理が呼ばれ、 validateResolved() メソッドが叩かれます。 https://github.com/laravel/framework/blob/11.x/src/Illuminate/Container/Container.php#L1276

validateResolved() は以下のような処理になっています。

    /**
     * Validate the class instance.
     *
     * @return void
     */
    public function validateResolved()
    {
        $this->prepareForValidation();

        if (! $this->passesAuthorization()) {
            $this->failedAuthorization();
        }

        $instance = $this->getValidatorInstance();

        if ($this->isPrecognitive()) {
            $instance->after(Precognition::afterValidationHook($this));
        }

        if ($instance->fails()) {
            $this->failedValidation($instance);
        }

        $this->passedValidation();
    }

https://github.com/laravel/framework/blob/v11.0.7/src/Illuminate/Validation/ValidatesWhenResolvedTrait.php#L17-L36

fails() => passes() という感じで呼び出され、rulesを使ったバリデーションが行われ、バリデーションエラーは $messages プロパティにセットされます。 https://github.com/laravel/framework/blob/v11.0.7/src/Illuminate/Validation/Validator.php#L436-L483

参考

LaravelのFormRequestでバリデートまでのソースコードを読む

このエントリーをはてなブックマークに追加