はじめに
新しい Laravel プロジェクトを開始すると、エラーと例外の処理がすでに設定されています。ただし、アプリケーションの bootstrap/app.php
で withExceptions
メソッドを使用して、例外がどのように報告され、レンダリングされるかを管理できます。
withExceptions
クロージャに提供される $exceptions
オブジェクトは Illuminate\Foundation\Configuration\Exceptions
のインスタンスであり、アプリケーションの例外処理を管理する責任があります。このドキュメント全体でこのオブジェクトについて詳しく説明します。
設定
アプリケーションの config/app.php
設定ファイル内の debug
オプションは、エラーに関する情報が実際にユーザーに表示される量を決定します。デフォルトでは、このオプションは APP_DEBUG
環境変数の値を尊重するように設定されており、これは .env
ファイルに保存されています。
ローカル開発中は、APP_DEBUG
環境変数を true
に設定する必要があります。本番環境では、この値は常に false
であるべきです。もし本番環境で true
に設定されている場合、アプリケーションのエンドユーザーに対して機密設定値を公開するリスクがあります。
例外の処理
例外の報告
Laravel では、例外報告を使用して例外をログに記録したり、外部サービス Sentry や Flare に送信したりします。デフォルトでは、例外は logging 設定に基づいてログに記録されます。ただし、例外を任意の方法でログに記録することができます。
異なる種類の例外を異なる方法で報告する必要がある場合、アプリケーションの 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;
});
})
特定の例外の例外報告をカスタマイズするには、reportable exceptions を利用することもできます。
グローバルログコンテキスト
利用可能な場合、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); // ignored
report($caught); // ignored
例外ログレベル
メッセージがアプリケーションの logs に書き込まれるとき、メッセージは指定された log level で書き込まれ、これはログに記録されるメッセージの重要度や重要性を示します。
上記のように、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
メソッドに渡されるクロージャは、Illuminate\Http\Response
のインスタンスを返す必要があります。これは 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 は例外が HTML または JSON レスポンスとしてレンダリングされるべきかをリクエストの Accept
ヘッダーに基づいて自動的に判断します。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 の service container によって自動的にメソッドに注入されます。
報告された例外のスロットリング
アプリケーションが非常に多くの例外を報告する場合、実際にログに記録される例外の数やアプリケーションの外部エラートラッキングサービスに送信される例外の数をスロットリングしたい場合があります。
例外のランダムサンプルレートを取得するには、アプリケーションの 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);
}
});
})
外部エラートラッキングサービスにログまたは送信される例外のレート制限を行うには、Limit
インスタンスを返す代わりに Lottery
を返します。これは、アプリケーションで使用されるサードパーティサービスがダウンしているときなど、突然の例外のバーストがログに溢れないように保護するのに役立ちます:
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>
Laravel のデフォルトのエラーページテンプレートを vendor:publish
Artisan コマンドを使用して公開できます。テンプレートが公開されたら、好みに応じてカスタマイズできます:
php artisan vendor:publish --tag=laravel-errors
フォールバック HTTP エラーページ
特定の HTTP ステータスコードのシリーズに対して「フォールバック」エラーページを定義することもできます。このページは、特定の HTTP ステータスコードに対応するページがない場合にレンダリングされます。これを実現するには、アプリケーションの resources/views/errors
ディレクトリに 4xx.blade.php
テンプレートと 5xx.blade.php
テンプレートを定義します。
フォールバックエラーページを定義する際、フォールバックページは 404
、500
、および 503
エラーレスポンスには影響しません。Laravel には、これらのステータスコード用の内部専用ページがあります。これらのステータスコードに対してレンダリングされるページをカスタマイズするには、それぞれのページに対して個別にカスタムエラーページを定義する必要があります。