はじめに

新しい Laravel プロジェクトを開始すると、エラーと例外の処理がすでに設定されています。ただし、アプリケーションの bootstrap/app.phpwithExceptions メソッドを使用して、例外がどのように報告され、レンダリングされるかを管理できます。

withExceptions クロージャに提供される $exceptions オブジェクトは Illuminate\Foundation\Configuration\Exceptions のインスタンスであり、アプリケーションの例外処理を管理する責任があります。このドキュメント全体でこのオブジェクトについて詳しく説明します。

設定

アプリケーションの config/app.php 設定ファイル内の debug オプションは、エラーに関する情報が実際にユーザーに表示される量を決定します。デフォルトでは、このオプションは APP_DEBUG 環境変数の値を尊重するように設定されており、これは .env ファイルに保存されています。

ローカル開発中は、APP_DEBUG 環境変数を true に設定する必要があります。本番環境では、この値は常に false であるべきです。もし本番環境で true に設定されている場合、アプリケーションのエンドユーザーに対して機密設定値を公開するリスクがあります。

例外の処理

例外の報告

Laravel では、例外報告を使用して例外をログに記録したり、外部サービス SentryFlare に送信したりします。デフォルトでは、例外は logging 設定に基づいてログに記録されます。ただし、例外を任意の方法でログに記録することができます。

異なる種類の例外を異なる方法で報告する必要がある場合、アプリケーションの bootstrap/app.phpreport 例外メソッドを使用して、特定のタイプの例外が報告される必要があるときに実行されるクロージャを登録できます。Laravel は、クロージャが報告する例外のタイプをクロージャのタイプヒントを調べることで判断します:

  1. ->withExceptions(function (Exceptions $exceptions) {
  2. $exceptions->report(function (InvalidOrderException $e) {
  3. // ...
  4. });
  5. })

report メソッドを使用してカスタム例外報告コールバックを登録すると、Laravel はアプリケーションのデフォルトのログ設定を使用して例外をログに記録します。デフォルトのログスタックへの例外の伝播を停止したい場合は、報告コールバックを定義するときに stop メソッドを使用するか、コールバックから false を返すことができます:

  1. ->withExceptions(function (Exceptions $exceptions) {
  2. $exceptions->report(function (InvalidOrderException $e) {
  3. // ...
  4. })->stop();
  5. $exceptions->report(function (InvalidOrderException $e) {
  6. return false;
  7. });
  8. })

特定の例外の例外報告をカスタマイズするには、reportable exceptions を利用することもできます。

グローバルログコンテキスト

利用可能な場合、Laravel は現在のユーザーの ID をすべての例外のログメッセージにコンテキストデータとして自動的に追加します。アプリケーションの bootstrap/app.php ファイル内で context 例外メソッドを使用して独自のグローバルコンテキストデータを定義できます。この情報は、アプリケーションによって書き込まれるすべての例外のログメッセージに含まれます:

  1. ->withExceptions(function (Exceptions $exceptions) {
  2. $exceptions->context(fn () => [
  3. 'foo' => 'bar',
  4. ]);
  5. })

例外ログコンテキスト

すべてのログメッセージにコンテキストを追加することは便利ですが、特定の例外にはログに含めたいユニークなコンテキストがある場合があります。アプリケーションの例外の1つに context メソッドを定義することで、その例外に関連するデータを指定し、例外のログエントリに追加することができます:

  1. <?php
  2. namespace App\Exceptions;
  3. use Exception;
  4. class InvalidOrderException extends Exception
  5. {
  6. // ...
  7. /**
  8. * Get the exception's context information.
  9. *
  10. * @return array<string, mixed>
  11. */
  12. public function context(): array
  13. {
  14. return ['order_id' => $this->orderId];
  15. }
  16. }

report ヘルパー

時には、例外を報告する必要があるが、現在のリクエストの処理を続ける必要がある場合があります。report ヘルパー関数を使用すると、ユーザーにエラーページをレンダリングせずに例外を迅速に報告できます:

  1. public function isValid(string $value): bool
  2. {
  3. try {
  4. // Validate the value...
  5. } catch (Throwable $e) {
  6. report($e);
  7. return false;
  8. }
  9. }

報告された例外の重複排除

アプリケーション全体で report 関数を使用している場合、同じ例外を複数回報告することがあり、ログに重複エントリが作成されることがあります。

例外の単一インスタンスが一度だけ報告されることを保証したい場合は、アプリケーションの bootstrap/app.php ファイル内で dontReportDuplicates 例外メソッドを呼び出すことができます:

  1. ->withExceptions(function (Exceptions $exceptions) {
  2. $exceptions->dontReportDuplicates();
  3. })

report ヘルパーが同じ例外のインスタンスで呼び出されると、最初の呼び出しのみが報告されます:

  1. $original = new RuntimeException('Whoops!');
  2. report($original); // reported
  3. try {
  4. throw $original;
  5. } catch (Throwable $caught) {
  6. report($caught); // ignored
  7. }
  8. report($original); // ignored
  9. report($caught); // ignored

例外ログレベル

メッセージがアプリケーションの logs に書き込まれるとき、メッセージは指定された log level で書き込まれ、これはログに記録されるメッセージの重要度や重要性を示します。

上記のように、report メソッドを使用してカスタム例外報告コールバックを登録しても、Laravel はアプリケーションのデフォルトのログ設定を使用して例外をログに記録します。ただし、ログレベルはメッセージがログに記録されるチャネルに影響を与えることがあるため、特定の例外がログに記録されるログレベルを設定したい場合があります。

これを実現するには、アプリケーションの bootstrap/app.php ファイル内で level 例外メソッドを使用できます。このメソッドは、最初の引数として例外タイプを、2番目の引数としてログレベルを受け取ります:

  1. use PDOException;
  2. use Psr\Log\LogLevel;
  3. ->withExceptions(function (Exceptions $exceptions) {
  4. $exceptions->level(PDOException::class, LogLevel::CRITICAL);
  5. })

タイプによる例外の無視

アプリケーションを構築する際に、報告したくない例外のタイプがいくつかあります。これらの例外を無視するには、アプリケーションの bootstrap/app.php ファイル内で dontReport 例外メソッドを使用できます。このメソッドに提供されたクラスは決して報告されませんが、カスタムレンダリングロジックを持つことはできます:

  1. use App\Exceptions\InvalidOrderException;
  2. ->withExceptions(function (Exceptions $exceptions) {
  3. $exceptions->dontReport([
  4. InvalidOrderException::class,
  5. ]);
  6. })

または、Illuminate\Contracts\Debug\ShouldntReport インターフェースで例外クラスを単に「マーク」することもできます。このインターフェースでマークされた例外は、Laravel の例外ハンドラによって決して報告されません:

  1. <?php
  2. namespace App\Exceptions;
  3. use Exception;
  4. use Illuminate\Contracts\Debug\ShouldntReport;
  5. class PodcastProcessingException extends Exception implements ShouldntReport
  6. {
  7. //
  8. }

内部的に、Laravel はすでに 404 HTTP エラーや無効な CSRF トークンによって生成された 419 HTTP レスポンスなど、いくつかのタイプのエラーを無視しています。特定のタイプの例外を無視するのを Laravel に指示したい場合は、アプリケーションの bootstrap/app.php ファイル内で stopIgnoring 例外メソッドを使用できます:

  1. use Symfony\Component\HttpKernel\Exception\HttpException;
  2. ->withExceptions(function (Exceptions $exceptions) {
  3. $exceptions->stopIgnoring(HttpException::class);
  4. })

例外のレンダリング

デフォルトでは、Laravel の例外ハンドラは例外を HTTP レスポンスに変換します。ただし、特定のタイプの例外に対してカスタムレンダリングクロージャを登録することができます。これを実現するには、アプリケーションの bootstrap/app.php ファイル内で render 例外メソッドを使用します。

render メソッドに渡されるクロージャは、Illuminate\Http\Response のインスタンスを返す必要があります。これは response ヘルパーを介して生成できます。Laravel は、クロージャがレンダリングする例外のタイプをクロージャのタイプヒントを調べることで判断します:

  1. use App\Exceptions\InvalidOrderException;
  2. use Illuminate\Http\Request;
  3. ->withExceptions(function (Exceptions $exceptions) {
  4. $exceptions->render(function (InvalidOrderException $e, Request $request) {
  5. return response()->view('errors.invalid-order', status: 500);
  6. });
  7. })

render メソッドを使用して、NotFoundHttpException のような組み込みの Laravel または Symfony 例外のレンダリング動作をオーバーライドすることもできます。render メソッドに渡されたクロージャが値を返さない場合、Laravel のデフォルトの例外レンダリングが利用されます:

  1. use Illuminate\Http\Request;
  2. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  3. ->withExceptions(function (Exceptions $exceptions) {
  4. $exceptions->render(function (NotFoundHttpException $e, Request $request) {
  5. if ($request->is('api/*')) {
  6. return response()->json([
  7. 'message' => 'Record not found.'
  8. ], 404);
  9. }
  10. });
  11. })

JSON としての例外のレンダリング

例外をレンダリングする際、Laravel は例外が HTML または JSON レスポンスとしてレンダリングされるべきかをリクエストの Accept ヘッダーに基づいて自動的に判断します。Laravel が HTML または JSON 例外レスポンスをレンダリングするかどうかをカスタマイズしたい場合は、shouldRenderJsonWhen メソッドを利用できます:

  1. use Illuminate\Http\Request;
  2. use Throwable;
  3. ->withExceptions(function (Exceptions $exceptions) {
  4. $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
  5. if ($request->is('admin/*')) {
  6. return true;
  7. }
  8. return $request->expectsJson();
  9. });
  10. })

例外レスポンスのカスタマイズ

まれに、Laravel の例外ハンドラによってレンダリングされる全体の HTTP レスポンスをカスタマイズする必要があります。これを実現するには、respond メソッドを使用してレスポンスカスタマイズクロージャを登録します:

  1. use Symfony\Component\HttpFoundation\Response;
  2. ->withExceptions(function (Exceptions $exceptions) {
  3. $exceptions->respond(function (Response $response) {
  4. if ($response->getStatusCode() === 419) {
  5. return back()->with([
  6. 'message' => 'The page expired, please try again.',
  7. ]);
  8. }
  9. return $response;
  10. });
  11. })

報告可能およびレンダリング可能な例外

アプリケーションの bootstrap/app.php ファイル内でカスタム報告およびレンダリング動作を定義する代わりに、アプリケーションの例外に直接 report および render メソッドを定義できます。これらのメソッドが存在する場合、フレームワークによって自動的に呼び出されます:

  1. <?php
  2. namespace App\Exceptions;
  3. use Exception;
  4. use Illuminate\Http\Request;
  5. use Illuminate\Http\Response;
  6. class InvalidOrderException extends Exception
  7. {
  8. /**
  9. * Report the exception.
  10. */
  11. public function report(): void
  12. {
  13. // ...
  14. }
  15. /**
  16. * Render the exception into an HTTP response.
  17. */
  18. public function render(Request $request): Response
  19. {
  20. return response(/* ... */);
  21. }
  22. }

例外がすでにレンダリング可能な例外(組み込みの Laravel または Symfony 例外など)を拡張している場合、例外の render メソッドから false を返すことで、例外のデフォルトの HTTP レスポンスをレンダリングできます:

  1. /**
  2. * Render the exception into an HTTP response.
  3. */
  4. public function render(Request $request): Response|bool
  5. {
  6. if (/** Determine if the exception needs custom rendering */) {
  7. return response(/* ... */);
  8. }
  9. return false;
  10. }

例外に特定の条件が満たされたときのみ必要なカスタム報告ロジックが含まれている場合、Laravel にデフォルトの例外処理設定を使用して例外を報告するよう指示する必要があります。これを実現するには、例外の report メソッドから false を返します:

  1. /**
  2. * Report the exception.
  3. */
  4. public function report(): bool
  5. {
  6. if (/** Determine if the exception needs custom reporting */) {
  7. // ...
  8. return true;
  9. }
  10. return false;
  11. }

report メソッドの必要な依存関係をタイプヒントすることができ、Laravel の service container によって自動的にメソッドに注入されます。

報告された例外のスロットリング

アプリケーションが非常に多くの例外を報告する場合、実際にログに記録される例外の数やアプリケーションの外部エラートラッキングサービスに送信される例外の数をスロットリングしたい場合があります。

例外のランダムサンプルレートを取得するには、アプリケーションの bootstrap/app.php ファイル内で throttle 例外メソッドを使用します。throttle メソッドは、Lottery インスタンスを返すクロージャを受け取ります:

  1. use Illuminate\Support\Lottery;
  2. use Throwable;
  3. ->withExceptions(function (Exceptions $exceptions) {
  4. $exceptions->throttle(function (Throwable $e) {
  5. return Lottery::odds(1, 1000);
  6. });
  7. })

特定の例外クラスのインスタンスのみをサンプリングしたい場合は、そのクラスに対してのみ Lottery インスタンスを返すことができます:

  1. use App\Exceptions\ApiMonitoringException;
  2. use Illuminate\Support\Lottery;
  3. use Throwable;
  4. ->withExceptions(function (Exceptions $exceptions) {
  5. $exceptions->throttle(function (Throwable $e) {
  6. if ($e instanceof ApiMonitoringException) {
  7. return Lottery::odds(1, 1000);
  8. }
  9. });
  10. })

外部エラートラッキングサービスにログまたは送信される例外のレート制限を行うには、Limit インスタンスを返す代わりに Lottery を返します。これは、アプリケーションで使用されるサードパーティサービスがダウンしているときなど、突然の例外のバーストがログに溢れないように保護するのに役立ちます:

  1. use Illuminate\Broadcasting\BroadcastException;
  2. use Illuminate\Cache\RateLimiting\Limit;
  3. use Throwable;
  4. ->withExceptions(function (Exceptions $exceptions) {
  5. $exceptions->throttle(function (Throwable $e) {
  6. if ($e instanceof BroadcastException) {
  7. return Limit::perMinute(300);
  8. }
  9. });
  10. })

デフォルトでは、制限は例外のクラスをレート制限キーとして使用します。Limitby メソッドを使用して独自のキーを指定することで、これをカスタマイズできます:

  1. use Illuminate\Broadcasting\BroadcastException;
  2. use Illuminate\Cache\RateLimiting\Limit;
  3. use Throwable;
  4. ->withExceptions(function (Exceptions $exceptions) {
  5. $exceptions->throttle(function (Throwable $e) {
  6. if ($e instanceof BroadcastException) {
  7. return Limit::perMinute(300)->by($e->getMessage());
  8. }
  9. });
  10. })

もちろん、異なる例外に対して Lottery および Limit インスタンスの混合を返すこともできます:

  1. use App\Exceptions\ApiMonitoringException;
  2. use Illuminate\Broadcasting\BroadcastException;
  3. use Illuminate\Cache\RateLimiting\Limit;
  4. use Illuminate\Support\Lottery;
  5. use Throwable;
  6. ->withExceptions(function (Exceptions $exceptions) {
  7. $exceptions->throttle(function (Throwable $e) {
  8. return match (true) {
  9. $e instanceof BroadcastException => Limit::perMinute(300),
  10. $e instanceof ApiMonitoringException => Lottery::odds(1, 1000),
  11. default => Limit::none(),
  12. };
  13. });
  14. })

HTTP 例外

一部の例外は、サーバーからの HTTP エラーコードを説明します。たとえば、これは「ページが見つかりません」エラー (404)、または「認証エラー」 (401)、さらには開発者が生成した 500 エラーである可能性があります。アプリケーションのどこからでもこのようなレスポンスを生成するには、abort ヘルパーを使用します:

  1. abort(404);

カスタム HTTP エラーページ

Laravel は、さまざまな HTTP ステータスコードのカスタムエラーページを表示するのを簡単にします。たとえば、404 HTTP ステータスコードのエラーページをカスタマイズするには、resources/views/errors/404.blade.php ビュー テンプレートを作成します。このビューは、アプリケーションによって生成されたすべての 404 エラーに対してレンダリングされます。このディレクトリ内のビューは、対応する HTTP ステータスコードに一致するように名前を付ける必要があります。abort 関数によって発生した Symfony\Component\HttpKernel\Exception\HttpException インスタンスは、$exception 変数としてビューに渡されます:

  1. <h2>{{ $exception->getMessage() }}</h2>

Laravel のデフォルトのエラーページテンプレートを vendor:publish Artisan コマンドを使用して公開できます。テンプレートが公開されたら、好みに応じてカスタマイズできます:

  1. php artisan vendor:publish --tag=laravel-errors

フォールバック HTTP エラーページ

特定の HTTP ステータスコードのシリーズに対して「フォールバック」エラーページを定義することもできます。このページは、特定の HTTP ステータスコードに対応するページがない場合にレンダリングされます。これを実現するには、アプリケーションの resources/views/errors ディレクトリに 4xx.blade.php テンプレートと 5xx.blade.php テンプレートを定義します。

フォールバックエラーページを定義する際、フォールバックページは 404500、および 503 エラーレスポンスには影響しません。Laravel には、これらのステータスコード用の内部専用ページがあります。これらのステータスコードに対してレンダリングされるページをカスタマイズするには、それぞれのページに対して個別にカスタムエラーページを定義する必要があります。