エラー処理
はじめに
新しいLaravelプロジェクトを開始すると、エラーと例外の処理は既に設定されています。ただし、いつでもアプリケーションの`bootstrap/app.php`内の`withExceptions`メソッドを使用して、アプリケーションによる例外のレポート方法とレンダリング方法を管理できます。
`withExceptions`クロージャに提供される`$exceptions`オブジェクトは、`Illuminate\Foundation\Configuration\Exceptions`のインスタンスであり、アプリケーションでの例外処理の管理を担当します。このオブジェクトについては、このドキュメント全体で詳しく説明します。
設定
`config/app.php`設定ファイルの`debug`オプションは、エラーに関する情報がユーザーに表示される量を決定します。デフォルトでは、このオプションは`.env`ファイルに格納されている`APP_DEBUG`環境変数の値を尊重するように設定されています。
ローカル開発中は、`APP_DEBUG`環境変数を`true`に設定する必要があります。**本番環境では、この値は常に`false`にする必要があります。本番環境で値が`true`に設定されている場合、アプリケーションのエンドユーザーに機密の設定値を公開するリスクがあります。**
例外の処理
例外のレポート
Laravelでは、例外レポートを使用して例外をログに記録するか、外部サービスSentryまたはFlareに送信します。デフォルトでは、例外はロギング設定に基づいてログに記録されます。ただし、必要に応じて例外をログに記録できます。
異なる方法で異なるタイプの例外を報告する必要がある場合は、アプリケーションの`bootstrap/app.php`内の`report`例外メソッドを使用して、特定のタイプの例外を報告する必要があるときに実行されるクロージャを登録できます。Laravelは、クロージャの型ヒントを調べることで、クロージャが報告する例外のタイプを判断します。
->withExceptions(function (Exceptions $exceptions) { $exceptions->report(function (InvalidOrderException $e) { // ... });})
`report`メソッドを使用してカスタム例外レポートコールバックを登録した場合でも、Laravelはアプリケーションのデフォルトのロギング設定を使用して例外をログに記録します。例外のデフォルトのロギングスタックへの伝播を停止する場合は、レポートコールバックを定義するときに`stop`メソッドを使用するか、コールバックから`false`を返すことができます。
->withExceptions(function (Exceptions $exceptions) { $exceptions->report(function (InvalidOrderException $e) { // ... })->stop(); $exceptions->report(function (InvalidOrderException $e) { return false; });})
特定の例外の例外レポートをカスタマイズするには、レポート可能な例外も使用できます。
グローバルログコンテキスト
Laravelは、利用可能な場合、現在のユーザーIDをコンテキストデータとしてすべての例外のログメッセージに自動的に追加します。アプリケーションの`bootstrap/app.php`ファイル内の`context`例外メソッドを使用して、独自のグローバルコンテキストデータを設定できます。この情報は、アプリケーションによって書き込まれたすべての例外のログメッセージに含まれます。
->withExceptions(function (Exceptions $exceptions) { $exceptions->context(fn () => [ 'foo' => 'bar', ]);})
例外ログコンテキスト
すべてのログメッセージにコンテキストを追加することは便利ですが、特定の例外には、ログに含めたい独自のコンテキストがある場合があります。アプリケーションの例外の1つに`context`メソッドを定義することで、その例外に関連するデータを指定し、例外のログエントリに追加できます。
<?php namespace App\Exceptions; use Exception; class InvalidOrderException extends Exception{ // ... /** * Get the exception's context information. * * @return array<string, mixed> */ public function context(): array { return ['order_id' => $this->orderId]; }}
`report`ヘルパー
例外を報告する必要があるが、現在のリクエストの処理を継続する必要がある場合があります。`report`ヘルパー関数を使用すると、ユーザーにエラーページを表示せずに例外を迅速に報告できます。
public function isValid(string $value): bool{ try { // Validate the value... } catch (Throwable $e) { report($e); return false; }}
レポートされた例外の重複排除
アプリケーション全体で`report`関数を使用している場合、同じ例外を複数回報告し、ログに重複エントリを作成することがあります。
例外の単一インスタンスが一度だけ報告されるようにする場合は、アプリケーションの`bootstrap/app.php`ファイル内の`dontReportDuplicates`例外メソッドを呼び出すことができます。
->withExceptions(function (Exceptions $exceptions) { $exceptions->dontReportDuplicates();})
これで、同じ例外のインスタンスで`report`ヘルパーが呼び出された場合、最初の呼び出しのみが報告されます。
$original = new RuntimeException('Whoops!'); report($original); // reported try { throw $original;} catch (Throwable $caught) { report($caught); // ignored} report($original); // ignoredreport($caught); // ignored
例外ログレベル
メッセージがアプリケーションのログに書き込まれると、メッセージは指定されたログレベルで書き込まれます。これは、ログに記録されているメッセージの重大度または重要性を示します。
前述のように、`report`メソッドを使用してカスタム例外レポートコールバックを登録した場合でも、Laravelはアプリケーションのデフォルトのロギング設定を使用して例外をログに記録します。ただし、ログレベルはメッセージがログに記録されるチャネルに影響を与える可能性があるため、特定の例外がログに記録されるログレベルを設定する場合があります。
これを実現するには、アプリケーションの`bootstrap/app.php`ファイル内の`level`例外メソッドを使用できます。このメソッドは、例外タイプを最初の引数として、ログレベルを2番目の引数として受け取ります。
use PDOException;use Psr\Log\LogLevel; ->withExceptions(function (Exceptions $exceptions) { $exceptions->level(PDOException::class, LogLevel::CRITICAL);})
タイプによる例外の無視
アプリケーションを構築する際には、報告したくない例外の種類があります。これらの例外を無視するには、アプリケーションの`bootstrap/app.php`ファイル内の`dontReport`例外メソッドを使用できます。このメソッドに提供されたクラスは報告されませんが、カスタムレンダリングロジックを持つ可能性があります。
use App\Exceptions\InvalidOrderException; ->withExceptions(function (Exceptions $exceptions) { $exceptions->dontReport([ InvalidOrderException::class, ]);})
または、単に`Illuminate\Contracts\Debug\ShouldntReport`インターフェースで例外クラスを「マーク」することもできます。例外がこのインターフェースでマークされている場合、Laravelの例外ハンドラーによって報告されることはありません。
<?php namespace App\Exceptions; use Exception;use Illuminate\Contracts\Debug\ShouldntReport; class PodcastProcessingException extends Exception implements ShouldntReport{ //}
内部的に、Laravelは既に404 HTTPエラーまたは無効なCSRFトークンによって生成された419 HTTPレスポンスに起因する例外など、いくつかのタイプのエラーを無視しています。特定のタイプの例外の無視を停止するようにLaravelに指示する場合は、アプリケーションの`bootstrap/app.php`ファイル内の`stopIgnoring`例外メソッドを使用できます。
use Symfony\Component\HttpKernel\Exception\HttpException; ->withExceptions(function (Exceptions $exceptions) { $exceptions->stopIgnoring(HttpException::class);})
例外のレンダリング
デフォルトでは、Laravel例外ハンドラーは例外をHTTPレスポンスに変換します。ただし、特定のタイプの例外に対してカスタムレンダリングクロージャを自由に登録できます。これは、アプリケーションの`bootstrap/app.php`ファイル内の`render`例外メソッドを使用することで実現できます。
render
メソッドに渡されるクロージャは、response
ヘルパーを使って生成できる `Illuminate\Http\Response` のインスタンスを返す必要があります。Laravel は、クロージャの型ヒントを調べることで、クロージャがどのタイプの例外をレンダリングするのかを判断します。
use App\Exceptions\InvalidOrderException;use Illuminate\Http\Request; ->withExceptions(function (Exceptions $exceptions) { $exceptions->render(function (InvalidOrderException $e, Request $request) { return response()->view('errors.invalid-order', status: 500); });})
render
メソッドを使用して、NotFoundHttpException
などの Laravel または Symfony の組み込み例外のレンダリング動作をオーバーライドすることもできます。`render` メソッドに渡されたクロージャが値を返さない場合、Laravel のデフォルトの例外レンダリングが使用されます。
use Illuminate\Http\Request;use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; ->withExceptions(function (Exceptions $exceptions) { $exceptions->render(function (NotFoundHttpException $e, Request $request) { if ($request->is('api/*')) { return response()->json([ 'message' => 'Record not found.' ], 404); } });})
例外のJSONとしてレンダリング
例外をレンダリングする場合、Laravel はリクエストの `Accept` ヘッダーに基づいて、例外をHTMLレスポンスとしてレンダリングするか、JSONレスポンスとしてレンダリングするかを自動的に判断します。Laravel がHTMLまたはJSON例外レスポンスのレンダリング方法を決定する方法をカスタマイズしたい場合は、shouldRenderJsonWhen
メソッドを使用できます。
use Illuminate\Http\Request;use Throwable; ->withExceptions(function (Exceptions $exceptions) { $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) { if ($request->is('admin/*')) { return true; } return $request->expectsJson(); });})
例外レスポンスのカスタマイズ
まれに、Laravel の例外ハンドラーによってレンダリングされる HTTP レスポンス全体をカスタマイズする必要がある場合があります。これを実現するには、respond
メソッドを使用してレスポンスタマイズクロージャを登録できます。
use Symfony\Component\HttpFoundation\Response; ->withExceptions(function (Exceptions $exceptions) { $exceptions->respond(function (Response $response) { if ($response->getStatusCode() === 419) { return back()->with([ 'message' => 'The page expired, please try again.', ]); } return $response; });})
レポート可能およびレンダリング可能な例外
アプリケーションの `bootstrap/app.php` ファイルでカスタムレポートとレンダリングの動作を定義する代わりに、アプリケーションの例外に `report` メソッドと `render` メソッドを直接定義できます。これらのメソッドが存在する場合、フレームワークによって自動的に呼び出されます。
<?php namespace App\Exceptions; use Exception;use Illuminate\Http\Request;use Illuminate\Http\Response; class InvalidOrderException extends Exception{ /** * Report the exception. */ public function report(): void { // ... } /** * Render the exception into an HTTP response. */ public function render(Request $request): Response { return response(/* ... */); }}
例外が、Laravel または Symfony の組み込み例外など、既にレンダリング可能な例外を拡張している場合、例外の `render` メソッドから `false` を返すことで、例外のデフォルトの HTTP レスポンスをレンダリングできます。
/** * Render the exception into an HTTP response. */public function render(Request $request): Response|bool{ if (/** Determine if the exception needs custom rendering */) { return response(/* ... */); } return false;}
例外に、特定の条件が満たされた場合にのみ必要なカスタムレポートロジックが含まれている場合、Laravel にデフォルトの例外処理設定を使用して例外をレポートするように指示する必要がある場合があります。これを実現するには、例外の `report` メソッドから `false` を返すことができます。
/** * Report the exception. */public function report(): bool{ if (/** Determine if the exception needs custom reporting */) { // ... return true; } return false;}
report
メソッドに必要な依存関係を型ヒントで指定すると、Laravel の サービスコンテナ によってメソッドに自動的に挿入されます。
レポートされた例外のスロットリング
アプリケーションで非常に多くの例外が報告される場合、実際にログに記録されるか、アプリケーションの外部エラー追跡サービスに送信される例外数を調整したい場合があります。
例外のランダムサンプリング率を取得するには、アプリケーションの `bootstrap/app.php` ファイルで `throttle` 例外メソッドを使用できます。`throttle` メソッドは、`Lottery` インスタンスを返すクロージャを受け取ります。
use Illuminate\Support\Lottery;use Throwable; ->withExceptions(function (Exceptions $exceptions) { $exceptions->throttle(function (Throwable $e) { return Lottery::odds(1, 1000); });})
例外の種類に基づいて条件付きでサンプリングすることも可能です。特定の例外クラスのインスタンスのみをサンプリングする場合は、そのクラスに対してのみ `Lottery` インスタンスを返すことができます。
use App\Exceptions\ApiMonitoringException;use Illuminate\Support\Lottery;use Throwable; ->withExceptions(function (Exceptions $exceptions) { $exceptions->throttle(function (Throwable $e) { if ($e instanceof ApiMonitoringException) { return Lottery::odds(1, 1000); } });})
`Lottery` の代わりに `Limit` インスタンスを返すことで、ログに記録されるか、外部エラー追跡サービスに送信される例外のレート制限を行うこともできます。これは、アプリケーションで使用されるサードパーティサービスがダウンしている場合など、突然の例外のバーストがログを氾濫するのを防ぎたい場合に役立ちます。
use Illuminate\Broadcasting\BroadcastException;use Illuminate\Cache\RateLimiting\Limit;use Throwable; ->withExceptions(function (Exceptions $exceptions) { $exceptions->throttle(function (Throwable $e) { if ($e instanceof BroadcastException) { return Limit::perMinute(300); } });})
デフォルトでは、制限は例外のクラスをレート制限キーとして使用します。`Limit` の `by` メソッドを使用して独自のキーを指定することで、これをカスタマイズできます。
use Illuminate\Broadcasting\BroadcastException;use Illuminate\Cache\RateLimiting\Limit;use Throwable; ->withExceptions(function (Exceptions $exceptions) { $exceptions->throttle(function (Throwable $e) { if ($e instanceof BroadcastException) { return Limit::perMinute(300)->by($e->getMessage()); } });})
もちろん、さまざまな例外に対して `Lottery` と `Limit` インスタンスを混合して返すこともできます。
use App\Exceptions\ApiMonitoringException;use Illuminate\Broadcasting\BroadcastException;use Illuminate\Cache\RateLimiting\Limit;use Illuminate\Support\Lottery;use Throwable; ->withExceptions(function (Exceptions $exceptions) { $exceptions->throttle(function (Throwable $e) { return match (true) { $e instanceof BroadcastException => Limit::perMinute(300), $e instanceof ApiMonitoringException => Lottery::odds(1, 1000), default => Limit::none(), }; });})
HTTP例外
一部の例外は、サーバーからの HTTP エラーコードを示しています。「ページが見つかりません」エラー (404)、「権限がありません」エラー (401)、開発者によって生成された 500 エラーなどがあります。アプリケーションのどこからでもこのようなレスポンスを生成するには、`abort` ヘルパーを使用できます。
abort(404);
カスタムHTTPエラーページ
Laravel では、さまざまな HTTP ステータスコードのカスタムエラーページを簡単に表示できます。たとえば、404 HTTP ステータスコードのエラーページをカスタマイズするには、`resources/views/errors/404.blade.php` ビューテンプレートを作成します。このビューは、アプリケーションによって生成されたすべての 404 エラーに対してレンダリングされます。このディレクトリ内のビューは、対応する HTTP ステータスコードに一致するように名前を付ける必要があります。`abort` 関数によって発生した `Symfony\Component\HttpKernel\Exception\HttpException` インスタンスは、`$exception` 変数としてビューに渡されます。
<h2>{{ $exception->getMessage() }}</h2>
`vendor:publish` Artisan コマンドを使用して、Laravel のデフォルトのエラーページテンプレートを公開できます。テンプレートが公開されたら、好みに合わせてカスタマイズできます。
php artisan vendor:publish --tag=laravel-errors
フォールバック HTTP エラーページ
特定の一連の HTTP ステータスコードに対して「フォールバック」エラーページを定義することもできます。このページは、発生した特定の HTTP ステータスコードに対応するページがない場合にレンダリングされます。これを実現するには、アプリケーションの `resources/views/errors` ディレクトリに `4xx.blade.php` テンプレートと `5xx.blade.php` テンプレートを定義します。
フォールバックエラーページを定義する場合、Laravel にはこれらのステータスコード専用の内部ページがあるため、フォールバックページは `404`、`500`、`503` エラーレスポンスには影響しません。これらのステータスコードに対してレンダリングされるページをカスタマイズするには、それぞれに対してカスタムエラーページを定義する必要があります。