【Laravel】 エラーハンドラを使用してログや表示をカスタマイズ

エラー (例外) を投げたとき、どのように動かせばよいかを設定できます。500を返すのか、元のページに戻すのかなど見ていきましょう。

例外のHandler

Laravelでは、例外排出時の挙動を設定できるHandlerがapp/Exceptions/Handler.phpに用意されています。

$dontReport

$dontReportでは、例外排出時にログに出力しない例外を指定します。あくまでログに出力しないというだけで、例外が起こっても処理を進めるものではありません。

protected $dontReport = [
    \Spatie\LaravelIgnition\Exceptions\ViewException::class,
    // ...
];

$dontFlash

$dontFlashは、バリデーション失敗時にセッションに一時保管しない値を指定するものです。例えば、パスワードの入力を間違えた際に元の画面に戻ると、メールアドレスの入力は引き継いでいますが、パスワードの入力は消えていますね。

これを$dontFlashで指定します。

protected $dontFlash = [
    'current_password',
    'password',
    'password_confirmation',
];

register

registerメソッドでは、主にreportablerenderableを使用した例外排出時のログ出力やページ表示に関する挙動の登録を行います。

reportableでは、特定の例外が投げられた場合にどのようなログを出力するかのカスタマイズなどが可能です。デフォルトのログを出力する場合はtrueを返し、出力しない場合はfalseを返すか->stop()を付けます。

public function register()
{
    // ViewException発生時、普通のログの代わりにログに"hogehoge"を出力する
    $this->reportable(function (\Spatie\LaravelIgnition\Exceptions\ViewException $e) {
        Log::debug("hogehoge");
        return false;
    });

    $this->reportable(function (Throwable $e) {
        // デフォルトのログ出力をしない
        return false;
    });

    $this->reportable(function (Throwable $e) {
        // デフォルトのログ出力をしない
    })->stop();

    $this->reportable(function (Throwable $e) {
        // デフォルトのログ出力をする
        return true;
    });
}

PHPで扱う例外は、全てThrowableインターフェースを実装しています。

また、renderableでは、例外排出時にどのような内容を表示するのかを設定することができます。

$this->renderable(function (\Spatie\LaravelIgnition\Exceptions\ViewException $e, Request $request){
    Log::debug($request);
    return response('View error.', 500);
});

とはいえ、これだけではあまりに表示が簡素なので、ビューを返すことをおすすめします。

$this->renderable(function (\Spatie\LaravelIgnition\Exceptions\ViewException $e, Request $request){
    Log::debug($request);
    return response()->view('dashboard', [/* viewに渡す値 */], 500);
});

第2引数は、コントローラでview関数を用いた時と同様に値を入れることができます。

第3引数はステータスコードです。

第4引数は省略していますが、レスポンスヘッダーを設定することが可能です。

return view(...)だとステータスコードを入れるのが面倒です。そのため、response()->view(...)がおすすめです。

上の例では画面こそダッシュボードの表示になりますが、ステータスコードはしっかり500であることが分かります。

view dashboard500

例外のクラス

アプリケーションの状態に応じて、独自の例外を出したいということはよくあることです。Laravelでは、例外に関するクラスを作成する場合にもartisanを使用します。

php artisan make:exception HogeException

そうすると、app/Exceptionディレクトリ内に指定した名前の例外のクラスが追加されます。

namespace App\Exceptions;

use Exception;

class HogeException extends Exception
{
    //
}

この例外のクラスには実装がないため、自由に実装することができます。

use App\Models\User;
use Exception;

class HogeException extends Exception
{
    private $user = null;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}
例外排出側のコード
$user = Auth::user();
throw new HogeException($user);

メッセージの設定

継承しているExceptionは、PHP組み込みの例外のクラスです。そのため、PHPと同じようにメッセージを設定できます。

class HogeException extends Exception
{
    public function __construct()
    {
        // messageは元からあるメンバなので宣言不要
        $this->message = "aaabbbccc";
    }
}

context

contextでは、例外の出力内容に含めたい内容を返すことで、例外の内容を充実させることができます。

class HogeException extends Exception
{
    private $user = null;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function context()
    {
        return [
            'user' => $this->user,
            'foo' => 'bar',
            'hoge' => 'fuga',
        ];
    }
}
例外の内容
[2022-03-27 09:14:16] local.ERROR:  {"user":{"App\\Models\\User":{"id":1,"name":"mhgm","email":"xxxxxx@yyyyy.com","email_verified_at":"2022-03-26T13:27:10.000000Z","created_at":"2022-03-17T10:24:46.000000Z","updated_at":"2022-03-26T13:27:10.000000Z","tel":"hogehoge"}},"foo":"bar","hoge":"fuga","userId":1,"exception":"[object] (App\\Exceptions\\HogeException(code: 0):  at /var/www/html/app/Http/Controllers/SettingsController.php:20)
[stacktrace]
#0 /var/www/html/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\SettingsController->index()
// ...

横に長いですが、たしかにUserモデルの情報と、入れた文字列が出力に含まれていることが分かります。

例外ログの内容を変更

例外出力の内容を変更したい場合、reportメソッドを実装します。

Handler.phpで設定したreportableと実質同じです。

class HogeException extends Exception
{
    /**
     * ログの出力
     * 
     * @return boolean|void trueかvoidならデフォルトのログを出力しない、falseなら出力する (reportableと逆)
     */
    public function report()
    {
        Log::error('hogehoge');
        return true;
    }
}

また、reportではサービスコンテナが依存性の注入をしてくれます。

public function report(MyService $service)
{
    Log::error($service->getHoge());
    return false;
}

例外時の画面出力を変更

例外を排出した際に、その例外特有の画面を出力したい場合、renderメソッドで出力内容やステータスコードを設定します。また、renderではRequestを受け取ることが可能なため、どのようなリクエストボディなどが設定されていたかを、コントローラと同様に取得することができます。

Handler.phpで設定したrenderableと実質同じです。

class HogeException extends Exception
{
    /**
     * 例外排出時の表示内容
     * 
     * @param Illuminate\Http\Request $request
     * @return mixed
     */
    public function render(Request $request)
    {
        Log::debug($request->all());
        // シンプルな文字列だけを表示する場合
        return response('Hoge error.', 500);
    }
}

renderableと同様にviewを返すこともできます。

public function render(Request $request)
{
    // dashboardの画面を表示
    return response()->view('dashboard', [/* viewに渡すデータ */], 500);
}

reportと違い、サービスコンテナは依存性の注入をしてくれません。

例外を排出せずにログだけ出力

例外が発生したけど動作は止めたくない、でもログは出力したいという場合、report関数を用います。

try {
    throw new HogeException($user);
} catch (Exception $e) {
    report($e);
}

もし上のように自作のExceptionである場合、reportメソッドは実行されますがrenderメソッドは実行されません。

動作を中断してエラー出力

動作を中断して404を出したい等の場合、abort()関数を用いると楽です。どこからでも使用できます。

abort(404);

// メッセージ付きの場合
abort(500, 'hogehoge');

エラーページを作成する

404や500といったエラーページは、デフォルトではLaravelが用意したページが表示されます。しかし、本当にそのページをそのまま使うことはほぼ無いはずです。

カスタマイズするには、resources/views/errors内に404.blade.phpのような形式で作成します。

resources/views/errors/404.blade.php
Not Found
<div>
    {{ $exception->getMessage() }}
</div>

エラーページではAuthが使えないので注意が必要です。

自作したExceptionでそれらのページを表示するようにしたい場合、response()->viewで指定して表示すると可能です。

class HogeException extends Exception
{
    // ...

    public function render(Request $request)
    {
        return response()->view('errors.500', ['exception' => $this], 500);
    }
}

{{ $exception->getMessage() }}のように$exceptionを使用する場合、'exception'をビューに渡すデータに含める必要があります。

メッセージは、PHPの例外を継承した場合と同様に設定します。

class HogeException extends Exception
{
    public function __construct()
    {
        $this->message = "aaabbbccc";
    }
}

abort()関数を利用することでも可能ですが、その時点で処理を中断するという関数の意味を考えると適していません。

例外のあの画面を無効化する

開発環境で例外が発生した場合、例外のメッセージや出力位置、セッションの値などがページに表示されます。しかし、本番環境でそれを出力してしまうと大事故です。本番環境では必ず無効化しておく必要があります。

.envAPP_DEBUGfalseに設定しておきましょう。

.env
// ...
APP_DEBUG=false
// ...

まとめ

Laravelでは、例外を作成し、例外に応じた挙動を設定することができます。Laravel組み込みの例外の挙動を設定したい場合はHandlerresponsableなどに設定を、自作の例外の場合はそれぞれの例外のクラスに設定すると良いです。

laravel error thumb

役に立ったらシェアしよう!