FormRequestでvalidateされるまで
FormRequestクラスの誕生
public function dispatch(Route $route, $controller, $method)
{
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
//...
}
resolveClassMethodDependencies()
が怪しいので見ていきます。
protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
{
//...
return $this->resolveMethodDependencies(
$parameters, new ReflectionMethod($instance, $method)
);
}
resolveMethodDependencies()
の返り値をreturnしているようです。
ならば見ていきましょう。
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クラス
となります。
transforDependency()
にReflectionParameterクラスと$parameters
を渡しています。
今回は$parameters
には何も入っていない体でいきます。
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はコンテナで解決されているので解決方法を見ていきましょう。そうサービスプロバイダーです。
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
が呼ばれます。このメソッドはトレイとに実装されています。
public function validateResolved()
{
$this->prepareForValidation();
if (! $this->passesAuthorization()) {
$this->failedAuthorization();
}
$instance = $this->getValidatorInstance();
if ($instance->fails()) {
$this->failedValidation($instance);
}
$this->passedValidation();
}
prepareForValidation()
はデフォルトではなにもしないメソッドになってます。
バリデーションの前になにかしたいことがあれば、このメソッドをオーバーライドすればよさそうです。
getValidatorInstance()
でバリデータを取得していそうです。
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)
が呼ばれます。
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します。
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
にありました。