メインコンテンツまでスキップ

エラー処理

はじめに

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

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

構成

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

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

例外の処理

例外の報告

Laravelでは、例外の報告は例外をログに記録したり、外部サービスSentryFlareに送信するために使用されます。デフォルトでは、例外はログ構成に基づいて記録されます。ただし、例外を好きなようにログに記録することができます。

異なる種類の例外を異なる方法で報告する必要がある場合は、アプリケーションの bootstrap/app.phpreport 例外メソッドを使用して、特定のタイプの例外が報告される必要があるときに実行されるべきクロージャを登録できます。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);
}
});
})

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

    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());
}
});
})


もちろん、異なる例外に対してLotteryLimitのインスタンスを混在させて返すこともできます:

    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テンプレートを定義することで実現できます。

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