エラー処理
はじめに
新しい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); // ignored
report($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,
]);
})
内部的に、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', [], 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>
Laravelのデフォルトのエラーページテンプレートをvendor:publish
Artisanコマンドを使用して公開することができます。テンプレートを公開した後は、自分の好みに合わせてカスタマイズできます:
php artisan vendor:publish --tag=laravel-errors
フォールバック HTTP エラーページ
特定のHTTPステータスコードに対応するページがない場合、そのシリーズの「フォールバック」エラーページを定義することもできます。これは、アプリケーションのresources/views/errors
ディレクトリに4xx.blade.php
テンプレートと5xx.blade.php
テンプレートを定義することで実現できます。
フォールバックエラーページを定義する際、404
、500
、503
のエラーレスポンスには影響しません。なぜなら、Laravelにはこれらのステータスコード用に内部的に専用のページがあるからです。これらのステータスコードに対してレンダリングされるページをカスタマイズするには、それぞれにカスタムエラーページを定義する必要があります。