Laravelの ValidationException
のハンドリングが何をやっているのかコードリーディングメモ。
Illuminate\Foundation\Exceptions\Handler
のこの部分。
public function render($request, Throwable $e)
{
// ...(省略)...
return $this->finalizeRenderedResponse($request, match (true) {
$e instanceof HttpResponseException => $e->getResponse(),
$e instanceof AuthenticationException => $this->unauthenticated($request, $e),
$e instanceof ValidationException => $this->convertValidationExceptionToResponse($e, $request),
default => $this->renderExceptionResponse($request, $e),
}, $e);
}
convertValidationExceptionToResponse()
ではJSONを返さない場合は invalid()
が呼ばれます。
protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
if ($e->response) {
return $e->response;
}
return $this->shouldReturnJson($request, $e)
? $this->invalidJson($request, $e)
: $this->invalid($request, $e);
}
invalid()
はRedirectResponseを作成しています。
protected function invalid($request, ValidationException $exception)
{
return redirect($exception->redirectTo ?? url()->previous())
->withInput(Arr::except($request->input(), $this->dontFlash))
->withErrors($exception->errors(), $request->input('_error_bag', $exception->errorBag));
}
withInput()
は Illuminate\Session\Store::flashInput()
を呼び出し、 _old_input
にflashデータとして入力値をセットします。これによって入力値がリダイレクトGET後の一回限りのセッションデータとして利用できるようになります。
public function withInput(array $input = null)
{
$this->session->flashInput($this->removeFilesFromInput(
! is_null($input) ? $input : $this->request->input()
));
return $this;
}
https://github.com/laravel/framework/blob/v11.0.7/src/Illuminate/Http/RedirectResponse.php#L74-L81 https://github.com/laravel/framework/blob/11.x/src/Illuminate/Session/Store.php#L534-L537
withErrors()
も Illuminate\Session\Store::flash()
を叩いて ValidationException::errors()
の内容をゴニョゴニョしてセットしてます。
public function withErrors($provider, $key = 'default')
{
$value = $this->parseErrors($provider);
$errors = $this->session->get('errors', new ViewErrorBag);
if (! $errors instanceof ViewErrorBag) {
$errors = new ViewErrorBag;
}
$this->session->flash(
'errors', $errors->put($key, $value)
);
return $this;
}
https://github.com/laravel/framework/blob/v11.0.7/src/Illuminate/Http/RedirectResponse.php#L131-L146
Bladeで使える old()
関数はhelperファイルに定義されています
function old($key = null, $default = null)
{
return app('request')->old($key, $default);
}
https://github.com/laravel/framework/blob/v11.0.7/src/Illuminate/Foundation/helpers.php#L570-L573
InteractsWithFlashData::old()
では Illuminate\Session\Store::getOldInput()
が呼ばれます。
public function old($key = null, $default = null)
{
$default = $default instanceof Model ? $default->getAttribute($key) : $default;
return $this->hasSession() ? $this->session()->getOldInput($key, $default) : $default;
}
getOldInput()
ではflashのセッションデータである _old_input
を取得しています。これにより直前の入力値を取得できます。
public function getOldInput($key = null, $default = null)
{
return Arr::get($this->get('_old_input', []), $key, $default);
}
エラーの値に関してはBladeの $errors
で取得できますが、これは Illuminate\View\Middleware\ShareErrorsFromSession
によってviewの変数にshareされることで実現しています。
public function handle($request, Closure $next)
{
// If the current session has an "errors" variable bound to it, we will share
// its value with all view instances so the views can easily access errors
// without having to bind. An empty bag is set when there aren't errors.
$this->view->share(
'errors', $request->session()->get('errors') ?: new ViewErrorBag
);
// Putting the errors in the view for every view allows the developer to just
// assume that some errors are always available, which is convenient since
// they don't have to continually run checks for the presence of errors.
return $next($request);
}