1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravel6の例外処理

Posted at

Laravel6の例外処理の動作を確認したのでメモとして残しておく。
最新版については、リポジトリ等参のこと。

処理の流れ

ブラウザからのアクセスによって例外が発生した場合、Illuminate\Routing\Pipeline::handleException() で処理されるようだ。

※コンソール経由の場合は未確認

/**
 * Handle the given exception.
 *
 * @param  mixed  $passable
 * @param  \Exception  $e
 * @return mixed
 *
 * @throws \Exception
 */

protected function handleException($passable, Exception $e)
{
    if (! $this->container->bound(ExceptionHandler::class) ||
        ! $passable instanceof Request) {
        throw $e;
    }

    $handler = $this->container->make(ExceptionHandler::class);

    $handler->report($e);

    $response = $handler->render($passable, $e);

    if (method_exists($response, 'withException')) {
        $response->withException($e);
    }

    return $response;
}

Laravelインストール直後の状態では$handlerに渡されるのは App\Exceptions\Handler になるので、

App\Exceptions\Handler::report()

public function report(Exception $exception)
{
    parent::report($exception);
}

App\Exceptions\Handler::render()

public function render($request, Exception $exception)
{
    return parent::render($request, $exception);
}

の順序で実行される。

見ての通り、親クラスのreport() / render() を呼んでいるだけなので、

  • カスタマイズしたい場合、App\Exceptions\Handler を修正
  • インストール直後の状態ででのような動きをするのか、は親クラスの Illuminate\Foundation\Exceptions\Handler を見れば良い

となる。

次項で、Illuminate\Foundation\Exceptions\Handler の実装がどうなっているのか確認する。

デフォルト動作の確認(Illuminate\Foundation\Exceptions\Handler)

Illuminate\Foundation\Exceptions\Handler::report()

まずreport() について

下記ソースコードの通り。大雑把に言えば、

  • 発生した例外が shouldntReport に含まれている場合は何もせず終了
  • 発生した例外が report() メソッドを持つ場合、その結果を返す
  • 上記いずれにも当てはまらない場合、Loggerでログを吐く

となる。

    /**
     * Report or log an exception.
     *
     * @param  \Exception  $e
     * @return void
     *
     * @throws \Exception
     */
    public function report(Exception $e)
    {
        if ($this->shouldntReport($e)) {
            return;
        }

        if (is_callable($reportCallable = [$e, 'report'])) {
            return $this->container->call($reportCallable);
        }
    
        try {
            $logger = $this->container->make(LoggerInterface::class);
        } catch (Exception $ex) {
            throw $e;
        }
    
        $logger->error(
            $e->getMessage(),
            array_merge(
                $this->exceptionContext($e),
                $this->context(),
                ['exception' => $e]
            )
        );
    }

Illuminate\Foundation\Exceptions\Handler::render()

次に、Illuminate\Foundation\Exceptions\Handler::render() について。

下記ソースコードの通りだが、reportより少し複雑。大雑把には、

  • 発生した例外がrender() または toResponse() を持つ場合、その結果を返す

  • いくつかの特定の例外に対しては、固有の処理を行う

    ※後述するが、Laravelフレームワーク側の話が多いので、通常はあまり気にする必要がない

  • 上記いずれにも当てはまらない場合、$request->expectsJson() の結果に応じてprepareJsonResponse() またはprepareResponse() の結果を返す

    ※開発者が気にするのはだいたいの場合こっち

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \Exception
     */
    public function render($request, Exception $e)
    {
        if (method_exists($e, 'render') && $response = $e->render($request)) {
            return Router::toResponse($request, $response);
        } elseif ($e instanceof Responsable) {
            return $e->toResponse($request);
        }

        $e = $this->prepareException($e);

        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof AuthenticationException) {
            return $this->unauthenticated($request, $e);
        } elseif ($e instanceof ValidationException) {
            return $this->convertValidationExceptionToResponse($e, $request);
        }
        return $request->expectsJson()
                    ? $this->prepareJsonResponse($request, $e)
                    : $this->prepareResponse($request, $e);
    }

いくつかの特定の例外について

下記3種類の例外は、それぞれ固有の処理が行われているので注意。

Illuminate\Http\Exceptions\HttpResponseException

HttpResponseException型の例外が発生した場合は、例外クラスに実装されているgetResponse()の結果がレスポンスとして返る。

Illuminate\Auth\AuthenticationException

認証に失敗した場合。この例外の場合は、unauthenticated() に処理が渡され、ステータスコード401でレスポンスが返る。
https://readouble.com/laravel/6.x/ja/authentication.html
と合わせて理解しておくと良い。

Illuminate\Validation\ValidationException

バリデーションに失敗した場合。この例外の場合は、convertValidationExceptionToResponse() に処理が渡されて、入力内容とともに一つ前の画面にリダイレクトされる。
https://readouble.com/laravel/6.x/ja/validation.html#form-request-validation
と合わせて理解しておくと良い。

いくつかの特定の例外について(prepareException()で型が書き換えられるもの)

Illuminate\Database\Eloquent\ModelNotFoundException

DBのレコードが見つからない(findOrFail() でfailする)と発生するやつ。NotFoundHttpExceptionに書き換えられ、ステータスコード404でレスポンスが返る。

Illuminate\Auth\Access\AuthorizationException

許可されていない操作を実行しようとすると発生する例外。AccessDeniedHttpExceptionに書き換えられ、ステータスコード403でレスポンスが返る。
https://readouble.com/laravel/6.x/ja/authorization.html
この辺と合わせて理解しておくと良い

Illuminate\Session\TokenMismatchException

CSRFトークンの検証が失敗すると発生する例外。HttpExceptionに書き換えられ、ステータスコード419でレスポンスが返る。
https://readouble.com/laravel/6.x/ja/csrf.html
この辺とあわせて。

Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException

よくわからん。ホスト名が一致しないと発生する??ステータスコード404でレスポンスが返る。
https://www.larajapan.com/2019/07/01/changelog%E3%81%AF%E9%9D%A2%E7%99%BD%E3%81%84/
過去ステータスコードが 404 => 500 => 404 という経緯で変わってるらしく、この辺の話題ばっかりヒットする

prepareResponse() の動作について

設定値 APP_DEBUG により処理が分岐するので一応注意が必要。
発生した例外が HttpExceptionInterface を実装していない場合

  • デバッグモードtrueならデバッグ用の画面が返る
  • デバッグモードfalseならステータスコード500でレスポンスが返る
    発生した例外が HttpExceptionInterface を実装している場合、例外に定義されている内容に従ってレスポンスが返る。
    protected function prepareResponse($request, Exception $e)
    {
        if (! $this->isHttpException($e) && config('app.debug')) {
            return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
        }

        if (! $this->isHttpException($e)) {
            $e = new HttpException(500, $e->getMessage());
        }

        return $this->toIlluminateResponse(
            $this->renderHttpException($e), $e
        );
    }
1
4
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
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?