Skip to content

エラー処理

はじめに

新しい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.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;
    });
})

Note

特定の例外の例外報告をカスタマイズするために、報告可能な例外を利用することもできます。

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

利用可能な場合、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
{
    // ...

    /**
     * 例外のコンテキスト情報を取得します。
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return ['order_id' => $this->orderId];
    }
}

reportヘルパー

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

public function isValid(string $value): bool
{
    try {
        // 値を検証...
    } 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); // 報告される

try {
    throw $original;
} catch (Throwable $caught) {
    report($caught); // 無視される
}

report($original); // 無視される
report($caught); // 無視される

例外ログレベル

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

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

これを実現するには、アプリケーションのbootstrap/app.phpファイルでlevel例外メソッドを使用できます。このメソッドは、第1引数として例外タイプ、第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レスポンスなど、いくつかのタイプのエラーを無視しています。特定のタイプの例外を無視しないようにしたい場合は、アプリケーションの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はリクエストのAcceptヘッダに基づいて、例外をHTMLとしてレンダリングするかJSONレスポンスとしてレンダリングするかを自動的に判断します。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ファイルでカスタムレポートとレンダリングの動作を定義する代わりに、アプリケーションの例外に直接reportrenderメソッドを定義することができます。これらのメソッドが存在する場合、フレームワークによって自動的に呼び出されます。

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class InvalidOrderException extends Exception
{
    /**
     * 例外をレポートする。
     */
    public function report(): void
    {
        // ...
    }

    /**
     * 例外をHTTPレスポンスにレンダリングする。
     */
    public function render(Request $request): Response
    {
        return response(/* ... */);
    }
}

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

/**
 * 例外をHTTPレスポンスにレンダリングする。
 */
public function render(Request $request): Response|bool
{
    if (/** 例外がカスタムレンダリングを必要とするかどうかを判断 */) {

        return response(/* ... */);
    }

    return false;
}

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

/**
 * 例外をレポートする。
 */
public function report(): bool
{
    if (/** 例外がカスタムレポートを必要とするかどうかを判断 */) {

        // ...

        return true;
    }

    return false;
}

Note

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

外部エラー追跡サービスにログに記録される例外や送信される例外をレートリミットすることもできます。これは、例えばアプリケーションが使用しているサードパーティサービスがダウンしている場合に、ログが急増するのを防ぐのに役立ちます。

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

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

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

ユーザーノート