LoginSignup
6
3

More than 5 years have passed since last update.

[Laravel 5.7] レスポンス、ロギングを定義した例外の作成

Posted at

Laravelの例外は、

  • renderメソッド(レスポンスを生成する)
  • reportメソッド(ロギングする)

を実装することで、例外をスローした時の処理をひとまとめにできます。
例としてスローされたとき以下の動作をする例外を作成してみます。

  • 渡したビューをレスポンスとして返す
  • スローされたときSlackに通知する

※SlackのIncoming Webhookを設定しておいてください。
https://readouble.com/laravel/5.7/ja/logging.html

新しい例外の定義

artisanコマンドで作成します。

$ php artisan make:exception SampleException

App\Exception\SampleException.phpが作成されるので、開いて以下のようにします。

app/Exceptions/SampleException.php
<?php

namespace App\Exceptions;

use Exception;
use Throwable;
use Illuminate\View\View;

class SampleException extends Exception
{
    protected $view;

    public function __construct(
        View $view,
        string $message = 'Sample Error',
        int $code = 0,
        Throwable $previous = null
    ) {
        $this->view = $view;
        parent::__construct($message, $code, $previous);
    }

    public function report()
    {
        // logger()はロガーにアクセスするヘルパ関数
        logger()->critical(
            $this->getMessage(),
            ['exception' => $this]
        );
        logger()->channel('slack')->critical('Sample Critical Error.');
    }

    public function render()
    {
        return $this->view;
    }
}

確認のため、/sampleにアクセスしたら作成した例外が投げられるようにします。

routes/web.php
use App\Exceptions\SampleException;

Route::get('/sample', function () {
    throw new SampleException(view('error', ['text' => 'Sample Critical Error']));
});

/sampleにアクセスすると、ビューが返され、Slackにも通知されています。

ビュー
スクリーンショット 2019-02-19 17.51.44.png

Slack
スクリーンショット 2019-02-19 17.52.56.png

[おまけ] 例外発生時の流れ

Laravelで発生した例外はIlluminate/Foundation/Http/Kernelクラスのhandleメソッドでキャッチされます。
(コンソールアプリケーションの場合はIlluminate/Foundation/Console/Kernelクラスです)。

Illuminate/Foundation/Http/Kernel.php
public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();

        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) {
        // ログに書き込み
        $this->reportException($e);
        // レスポンスの生成
        $response = $this->renderException($request, $e);
    } catch (Throwable $e) {
        $this->reportException($e = new FatalThrowableError($e));

        $response = $this->renderException($request, $e);
    }

    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );

    return $response;
}

reportException, renderExceptionメソッドはそれぞれApp\Exceptions\Handlerクラスのreport, renderメソッドを呼び出します。

Illuminate/Foundation/Http/Kernel.php
protected function reportException(Exception $e)
{
     // bootstrap/app.phpでApp\Exceptions\Handler::classがバインドされている
    $this->app[ExceptionHandler::class]->report($e);
}

protected function renderException($request, Exception $e)
{
    return $this->app[ExceptionHandler::class]->render($request, $e);
}

App\Exceptions\Handlerクラスの親Illuminate/Foundation/Exceptions/Handlerクラスのreport, renderメソッドを見ると・・・
色々分岐して処理が書かれていますが、レスポンスの返却、ロギングをしています。

/**
 * ロギングする
 */
public function report(Exception $e)
{
    // ロギングしない例外なら何もしない
    if ($this->shouldntReport($e)) {
        return;
    }

    // 例外にreportメソッドがあれば実行
    if (method_exists($e, 'report')) {
        return $e->report();
    }

    // なければロガーを取得して・・・
    try {
        $logger = $this->container->make(LoggerInterface::class);
    } catch (Exception $ex) {
        throw $e;
    }

    // ここでロギング
    $logger->error(
        $e->getMessage(),
        array_merge($this->context(), ['exception' => $e]
    ));
}

/**
 * 例外にあったレスポンスを生成する
 */
public function render($request, Exception $e)
{
    // 例外がrenderメソッドかResponsableインターフェースを実装していれば、
    // その戻り値をレスポンスとして返す
    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);
}
6
3
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
6
3