LoginSignup
9
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-26

FormRequestでvalidateされるまで

FormRequestクラスの誕生

Illuminate/Routing/ControllerDispatcher.php
public function dispatch(Route $route, $controller, $method)
{
    $parameters = $this->resolveClassMethodDependencies(
        $route->parametersWithoutNulls(), $controller, $method
    );

    //...
}

resolveClassMethodDependencies()が怪しいので見ていきます。

Illuminate/Routing/RouteDependencyResolverTrait.php
protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
{
    //...

    return $this->resolveMethodDependencies(
        $parameters, new ReflectionMethod($instance, $method)
    );
}

resolveMethodDependencies()の返り値をreturnしているようです。

ならば見ていきましょう。

Illuminate/Routing/RouteDependencyResolverTrait.php
public function resolveMethodDependencies(array $parameters, ReflectionFunctionAbstract $reflector)
{
    $instanceCount = 0;

    $values = array_values($parameters);

    foreach ($reflector->getParameters() as $key => $parameter) {
        $instance = $this->transformDependency(
            $parameter, $parameters
        );

        if (! is_null($instance)) {
            $instanceCount++;

            $this->spliceIntoParameters($parameters, $key, $instance);
        } elseif (! isset($values[$key - $instanceCount]) &&
                  $parameter->isDefaultValueAvailable()) {
            $this->spliceIntoParameters($parameters, $key, $parameter->getDefaultValue());
        }
    }

    return $parameters;
}

まずparametersには基本的にルートモデルバインディングで解決されたものが入っています。

$parameters = ['user' => Userのインスタンス]

foreachのkeyとparameterの部分は、0 => ReflectionParameterクラスとなります。

ReflectionParameterクラス リファレンス

transforDependency()にReflectionParameterクラスと$parametersを渡しています。
今回は$parametersには何も入っていない体でいきます。

Illuminate/Routing/RouteDependencyResolverTrait.php
protected function transformDependency(ReflectionParameter $parameter, $parameters)
{
    $class = $parameter->getClass();

    if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
        return $parameter->isDefaultValueAvailable()
            ? $parameter->getDefaultValue()
            : $this->container->make($class->name);
    }
}

$parameter->getClass()でメソッドにタイプヒンティングしてあるクラス名を取得しています。
stringやintの場合はnullになります。

alreadyInParametersはその名の通り、すでにルートモデルバインディングで解決されたやつはtrueを返すやで〜ってやつなので今回は関係ないです。

return文のところは、デフォルト引数だとその値を使って、そうじゃなければクラス名からインスタンスを生成します。
(ここでFormRequestのインスタンスが生成されます)

そのままなんやかんやあってコントローラーメソッドに引き渡されていきます。

解決時の処理

FormRequestのインスタンスを生成するだけでバリデーションしてないじゃん!と思ったそこのあなた!

Laravelの魔法がここから始まります。

まずFormRequestはコンテナで解決されているので解決方法を見ていきましょう。そうサービスプロバイダーです。

Illuminate/Foundation/Providers/FormRequestServiceProvider.php
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));
    });
}

resolving, afterResolvingは第一引数に渡されたクラスがコンテナで解決されたときに発行されるイベントをもとに実行されます。

さきほど見たようにFormRequestが生成された時点でresolvingで登録したコールバックが呼ばれます。

ここのresolvingはFormRequestにコンテナとリダイレクタとリクエストをセットしているぐらいです。

FormRequestはValidatesWhenResolvedインタフェースを実装しているので、afterResolvingのコールバックも呼ばれます。

ここでFormRequest#validateResolvedが呼ばれます。このメソッドはトレイとに実装されています。

Illuminate/Validation/ValidatesWhenResolvedTrait.php
public function validateResolved()
{
    $this->prepareForValidation();

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

    $instance = $this->getValidatorInstance();

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

    $this->passedValidation();
}

prepareForValidation()はデフォルトではなにもしないメソッドになってます。
バリデーションの前になにかしたいことがあれば、このメソッドをオーバーライドすればよさそうです。

getValidatorInstance()でバリデータを取得していそうです。

Illuminate/Foundation/Http/FormRequest.php
protected function getValidatorInstance()
{
    //...

    $factory = $this->container->make(ValidationFactory::class);

    if (method_exists($this, 'validator')) {
        $validator = $this->container->call([$this, 'validator'], compact('factory'));
    } else {
        $validator = $this->createDefaultValidator($factory);
    }

    //...

    $this->setValidator($validator);

    return $this->validator;
}

FormRequestにはvalidator()がないので、$this->createDefaultValidator($factory)が呼ばれます。

Illuminate/Foundation/Http/FormRequest.php
protected function createDefaultValidator(ValidationFactory $factory)
{
    return $factory->make(
        $this->validationData(), $this->container->call([$this, 'rules']),
        $this->messages(), $this->attributes()
    );
}

これでIlluminate\Contracts\Validation\Validator.phpを作成して、プロパティにセットしつつreturnします。

Illuminate/Validation/ValidatesWhenResolvedTrait.php
public function validateResolved()
{
    $this->prepareForValidation();

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

    $instance = $this->getValidatorInstance();

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

    $this->passedValidation();
}

$instance->fails()でバリデーションを行って、バリデーションに引っかかればエラーが投げられます。

$this->passedValidation()はなにもしないメソッドなので、バリデーション後にしたい処理をオーバーライドして記述すればよいでしょう。

既存のルールの判定メソッドは Illuminate\Validation\Concerns\ValidatesAttributes にありました。

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5