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
が作成されるので、開いて以下のようにします。
<?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
にアクセスしたら作成した例外が投げられるようにします。
use App\Exceptions\SampleException;
Route::get('/sample', function () {
throw new SampleException(view('error', ['text' => 'Sample Critical Error']));
});
/sample
にアクセスすると、ビューが返され、Slackにも通知されています。
[おまけ] 例外発生時の流れ
Laravelで発生した例外はIlluminate/Foundation/Http/Kernel
クラスのhandleメソッドでキャッチされます。
(コンソールアプリケーションの場合はIlluminate/Foundation/Console/Kernel
クラスです)。
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メソッドを呼び出します。
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);
}