目次
- Laravel Exception の基礎知識
- Laravel Exception の基礎知識
- Laravel Exception の実践的な使い方
- Laravel Exception の基礎知識
- Laravel Exception の実践的な使い方
- エラー処理のベストプラクティス
- Laravel Exception の基礎知識
- Laravel Exception の実践的な使い方
- エラー処理のベストプラクティス
- 高度な例外処理手法
- Laravel Exception の基礎知識
- Laravel Exception の実践的な使い方
- エラー処理のベストプラクティス
- 高度な例外処理手法
- デバッグとトラブルシューティング
- Laravel Exception の基礎知識
- Laravel Exception の実践的な使い方
- エラー処理のベストプラクティス
- 高度な例外処理手法
- デバッグとトラブルシューティング
- Laravel 例外のテスト
Laravel Exception の基礎知識
Laravel におけるエラー処理の重要性
Webアプリケーションの開発において、エラー処理は非常に重要な要素です。特にLaravelのような本番環境で使用されるフレームワークでは、適切なエラー処理は以下の理由から不可欠となります:
- アプリケーションの信頼性向上
- 予期せぬエラーを適切にキャッチし処理
- システムの安定性を確保
- ユーザー体験の維持
- セキュリティの確保
- センシティブな情報の漏洩防止
- エラーメッセージによる脆弱性露出の回避
- 適切なログ記録によるセキュリティ監査の実現
- デバッグの効率化
- 開発環境での詳細なエラー情報の提供
- 本番環境での適切なエラーハンドリング
- 問題の早期発見と解決
例外処理の基本的な仕組み
LaravelのExceptionの仕組みは、PHPの例外処理メカニズムを基盤としながら、フレームワーク固有の機能を追加しています。
try {
// 潜在的にエラーが発生する可能性のあるコード
$result = $this->someRiskyOperation();
} catch (Exception $e) {
// エラーハンドリング
Log::error('エラーが発生しました: ' . $e->getMessage());
return response()->json(['error' => '処理に失敗しました'], 500);
} finally {
// 必ず実行される処理
$this->cleanup();
}
基本的な例外処理の流れ:
- 例外の発生
- 例外のキャッチ
- エラーハンドリング
- レスポンス生成
まさかの例外クラスと障害構造
Laravelには様々な組み込み例外クラスが用意されています:
- 基本的な例外クラス
Exception: すべての例外の基底クラスRuntimeException: 実行時の例外LogicException: プログラムのロジックエラー
- Laravel固有の例外クラス
// モデルが見つからない場合の例外 use Illuminate\Database\Eloquent\ModelNotFoundException; // バリデーション失敗時の例外 use Illuminate\Validation\ValidationException; // 認証・認可の例外 use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Access\AuthorizationException;
- 例外の階層構造
Exception
├── RuntimeException
│ ├── ModelNotFoundException
│ └── ValidationException
└── LogicException
└── AuthorizationException
これらの例外クラスは、それぞれ特定のエラー状況に対応するように設計されており、適切なHTTPステータスコードやエラーメッセージを持っています。
重要なポイント:
- 例外クラスは目的に応じて使い分ける
- カスタム例外は既存の例外クラスを継承して作成
- エラーメッセージは具体的かつ適切な情報を含める
以上が、Laravel Exceptionの基本的な概念と仕組みです。これらの理解は、より高度なエラー処理の実装の基礎となります。
Laravel Exception の基礎知識
Laravel におけるエラー処理の重要性
Webアプリケーションの開発において、エラー処理は非常に重要な要素です。特にLaravelのような本番環境で使用されるフレームワークでは、適切なエラー処理は以下の理由から不可欠となります:
- アプリケーションの信頼性向上
- 予期せぬエラーを適切にキャッチし処理
- システムの安定性を確保
- ユーザー体験の維持
- セキュリティの確保
- センシティブな情報の漏洩防止
- エラーメッセージによる脆弱性露出の回避
- 適切なログ記録によるセキュリティ監査の実現
- デバッグの効率化
- 開発環境での詳細なエラー情報の提供
- 本番環境での適切なエラーハンドリング
- 問題の早期発見と解決
例外処理の基本的な仕組み
LaravelのExceptionの仕組みは、PHPの例外処理メカニズムを基盤としながら、フレームワーク固有の機能を追加しています。
try {
// 潜在的にエラーが発生する可能性のあるコード
$result = $this->someRiskyOperation();
} catch (Exception $e) {
// エラーハンドリング
Log::error('エラーが発生しました: ' . $e->getMessage());
return response()->json(['error' => '処理に失敗しました'], 500);
} finally {
// 必ず実行される処理
$this->cleanup();
}
基本的な例外処理の流れ:
- 例外の発生
- 例外のキャッチ
- エラーハンドリング
- レスポンス生成
まさかの例外クラスと障害構造
Laravelには様々な組み込み例外クラスが用意されています:
- 基本的な例外クラス
Exception: すべての例外の基底クラスRuntimeException: 実行時の例外LogicException: プログラムのロジックエラー
- Laravel固有の例外クラス
// モデルが見つからない場合の例外 use Illuminate\Database\Eloquent\ModelNotFoundException; // バリデーション失敗時の例外 use Illuminate\Validation\ValidationException; // 認証・認可の例外 use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Access\AuthorizationException;
- 例外の階層構造
Exception
├── RuntimeException
│ ├── ModelNotFoundException
│ └── ValidationException
└── LogicException
└── AuthorizationException
これらの例外クラスは、それぞれ特定のエラー状況に対応するように設計されており、適切なHTTPステータスコードやエラーメッセージを持っています。
重要なポイント:
- 例外クラスは目的に応じて使い分ける
- カスタム例外は既存の例外クラスを継承して作成
- エラーメッセージは具体的かつ適切な情報を含める
以上が、Laravel Exceptionの基本的な概念と仕組みです。これらの理解は、より高度なエラー処理の実装の基礎となります。
Laravel Exception の実践的な使い方
カスタム例外クラスの作成方法
Laravelでは、アプリケーション固有のエラー状況に対応するために、カスタム例外クラスを作成することができます。
- 基本的なカスタム例外クラスの作成
<?php
namespace App\Exceptions;
use Exception;
class PaymentFailedException extends Exception
{
protected $message = '決済処理に失敗しました';
protected $code = 500;
public function report()
{
// エラーのログ記録
\Log::error('決済エラー: ' . $this->getMessage());
}
public function render($request)
{
// APIリクエストの場合のレスポンス
if ($request->expectsJson()) {
return response()->json([
'error' => $this->getMessage(),
'code' => $this->code
], $this->code);
}
// 通常のWebリクエストの場合のレスポンス
return view('errors.payment', [
'message' => $this->getMessage()
]);
}
}
- コンストラクタでのカスタマイズ
public function __construct($message = null, $code = 0, Exception $previous = null)
{
// カスタムメッセージがある場合は上書き
if (!is_null($message)) {
$this->message = $message;
}
parent::__construct($this->message, $code, $previous);
}
例外のキャッチと処理の実装例
実際のアプリケーションでの例外処理の実装例を見ていきましょう。
- サービスクラスでの使用例
class PaymentService
{
public function processPayment(Order $order)
{
try {
// 決済処理
$result = $this->paymentGateway->charge($order->amount);
if (!$result->isSuccessful()) {
throw new PaymentFailedException(
'決済が拒否されました: ' . $result->getMessage()
);
}
return $result;
} catch (PaymentFailedException $e) {
// アプリケーション固有の例外処理
report($e);
throw $e;
} catch (\Exception $e) {
// その他の予期せぬ例外の処理
report($e);
throw new PaymentFailedException(
'決済処理中に予期せぬエラーが発生しました',
500,
$e
);
}
}
}
- コントローラでの使用例
class OrderController extends Controller
{
public function store(OrderRequest $request)
{
try {
$order = $this->orderService->createOrder($request->validated());
$this->paymentService->processPayment($order);
return redirect()
->route('orders.show', $order)
->with('success', '注文が完了しました');
} catch (PaymentFailedException $e) {
return back()
->withInput()
->withErrors(['payment' => $e->getMessage()]);
}
}
}
HTTPステータスコードとの連携方法
Laravelでは、例外とHTTPステータスコードを適切に連携させることが重要です。
- レスポンストレイトの使用
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiException extends HttpException
{
public function __construct($message = null, $code = Response::HTTP_BAD_REQUEST)
{
parent::__construct($code, $message ?? 'APIエラーが発生しました');
}
}
- ステータスコードのマッピング例
// app/Exceptions/Handler.php
protected $statusCodeMapping = [
ModelNotFoundException::class => 404,
AuthorizationException::class => 403,
ValidationException::class => 422,
PaymentFailedException::class => 400
];
public function render($request, Throwable $e)
{
$statusCode = $this->statusCodeMapping[get_class($e)] ?? 500;
if ($request->expectsJson()) {
return response()->json([
'error' => $e->getMessage(),
'status' => $statusCode
], $statusCode);
}
return parent::render($request, $e);
}
実装のポイント:
- 例外クラスは目的に応じて適切に設計する
- レスポンスフォーマットを統一する
- ログ記録とエラー通知を適切に設定する
- APIとWeb画面で適切なレスポンスを返す
- セキュリティに配慮したエラーメッセージを設定する
これらの実装方法を理解し、適切に活用することで、堅牢なエラー処理システムを構築することができます。
Laravel Exception の基礎知識
Laravel におけるエラー処理の重要性
Webアプリケーションの開発において、エラー処理は非常に重要な要素です。特にLaravelのような本番環境で使用されるフレームワークでは、適切なエラー処理は以下の理由から不可欠となります:
- アプリケーションの信頼性向上
- 予期せぬエラーを適切にキャッチし処理
- システムの安定性を確保
- ユーザー体験の維持
- セキュリティの確保
- センシティブな情報の漏洩防止
- エラーメッセージによる脆弱性露出の回避
- 適切なログ記録によるセキュリティ監査の実現
- デバッグの効率化
- 開発環境での詳細なエラー情報の提供
- 本番環境での適切なエラーハンドリング
- 問題の早期発見と解決
例外処理の基本的な仕組み
LaravelのExceptionの仕組みは、PHPの例外処理メカニズムを基盤としながら、フレームワーク固有の機能を追加しています。
try {
// 潜在的にエラーが発生する可能性のあるコード
$result = $this->someRiskyOperation();
} catch (Exception $e) {
// エラーハンドリング
Log::error('エラーが発生しました: ' . $e->getMessage());
return response()->json(['error' => '処理に失敗しました'], 500);
} finally {
// 必ず実行される処理
$this->cleanup();
}
基本的な例外処理の流れ:
- 例外の発生
- 例外のキャッチ
- エラーハンドリング
- レスポンス生成
まさかの例外クラスと障害構造
Laravelには様々な組み込み例外クラスが用意されています:
- 基本的な例外クラス
Exception: すべての例外の基底クラスRuntimeException: 実行時の例外LogicException: プログラムのロジックエラー
- Laravel固有の例外クラス
// モデルが見つからない場合の例外 use Illuminate\Database\Eloquent\ModelNotFoundException; // バリデーション失敗時の例外 use Illuminate\Validation\ValidationException; // 認証・認可の例外 use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Access\AuthorizationException;
- 例外の階層構造
Exception
├── RuntimeException
│ ├── ModelNotFoundException
│ └── ValidationException
└── LogicException
└── AuthorizationException
これらの例外クラスは、それぞれ特定のエラー状況に対応するように設計されており、適切なHTTPステータスコードやエラーメッセージを持っています。
重要なポイント:
- 例外クラスは目的に応じて使い分ける
- カスタム例外は既存の例外クラスを継承して作成
- エラーメッセージは具体的かつ適切な情報を含める
以上が、Laravel Exceptionの基本的な概念と仕組みです。これらの理解は、より高度なエラー処理の実装の基礎となります。
Laravel Exception の実践的な使い方
カスタム例外クラスの作成方法
Laravelでは、アプリケーション固有のエラー状況に対応するために、カスタム例外クラスを作成することができます。
- 基本的なカスタム例外クラスの作成
<?php
namespace App\Exceptions;
use Exception;
class PaymentFailedException extends Exception
{
protected $message = '決済処理に失敗しました';
protected $code = 500;
public function report()
{
// エラーのログ記録
\Log::error('決済エラー: ' . $this->getMessage());
}
public function render($request)
{
// APIリクエストの場合のレスポンス
if ($request->expectsJson()) {
return response()->json([
'error' => $this->getMessage(),
'code' => $this->code
], $this->code);
}
// 通常のWebリクエストの場合のレスポンス
return view('errors.payment', [
'message' => $this->getMessage()
]);
}
}
- コンストラクタでのカスタマイズ
public function __construct($message = null, $code = 0, Exception $previous = null)
{
// カスタムメッセージがある場合は上書き
if (!is_null($message)) {
$this->message = $message;
}
parent::__construct($this->message, $code, $previous);
}
例外のキャッチと処理の実装例
実際のアプリケーションでの例外処理の実装例を見ていきましょう。
- サービスクラスでの使用例
class PaymentService
{
public function processPayment(Order $order)
{
try {
// 決済処理
$result = $this->paymentGateway->charge($order->amount);
if (!$result->isSuccessful()) {
throw new PaymentFailedException(
'決済が拒否されました: ' . $result->getMessage()
);
}
return $result;
} catch (PaymentFailedException $e) {
// アプリケーション固有の例外処理
report($e);
throw $e;
} catch (\Exception $e) {
// その他の予期せぬ例外の処理
report($e);
throw new PaymentFailedException(
'決済処理中に予期せぬエラーが発生しました',
500,
$e
);
}
}
}
- コントローラでの使用例
class OrderController extends Controller
{
public function store(OrderRequest $request)
{
try {
$order = $this->orderService->createOrder($request->validated());
$this->paymentService->processPayment($order);
return redirect()
->route('orders.show', $order)
->with('success', '注文が完了しました');
} catch (PaymentFailedException $e) {
return back()
->withInput()
->withErrors(['payment' => $e->getMessage()]);
}
}
}
HTTPステータスコードとの連携方法
Laravelでは、例外とHTTPステータスコードを適切に連携させることが重要です。
- レスポンストレイトの使用
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiException extends HttpException
{
public function __construct($message = null, $code = Response::HTTP_BAD_REQUEST)
{
parent::__construct($code, $message ?? 'APIエラーが発生しました');
}
}
- ステータスコードのマッピング例
// app/Exceptions/Handler.php
protected $statusCodeMapping = [
ModelNotFoundException::class => 404,
AuthorizationException::class => 403,
ValidationException::class => 422,
PaymentFailedException::class => 400
];
public function render($request, Throwable $e)
{
$statusCode = $this->statusCodeMapping[get_class($e)] ?? 500;
if ($request->expectsJson()) {
return response()->json([
'error' => $e->getMessage(),
'status' => $statusCode
], $statusCode);
}
return parent::render($request, $e);
}
実装のポイント:
- 例外クラスは目的に応じて適切に設計する
- レスポンスフォーマットを統一する
- ログ記録とエラー通知を適切に設定する
- APIとWeb画面で適切なレスポンスを返す
- セキュリティに配慮したエラーメッセージを設定する
これらの実装方法を理解し、適切に活用することで、堅牢なエラー処理システムを構築することができます。
エラー処理のベストプラクティス
意味のある例外メッセージの設計
効果的な例外メッセージは、問題の特定と解決を容易にします。
- メッセージ設計の原則
class OrderException extends Exception
{
public function __construct($orderId, $reason, $suggestion = null)
{
$message = sprintf(
'Order #%d failed: %s',
$orderId,
$reason
);
if ($suggestion) {
$message .= sprintf('. Suggestion: %s', $suggestion);
}
parent::__construct($message);
}
}
// 使用例
throw new OrderException(
$order->id,
'在庫不足',
'少量に分けて注文してください'
);
- コンテキスト情報の付加
class DataValidationException extends Exception
{
protected $errors;
public function __construct(array $errors, $message = '入力データが不正です')
{
$this->errors = $errors;
parent::__construct($message);
}
public function getValidationErrors()
{
return $this->errors;
}
public function render($request)
{
return response()->json([
'message' => $this->getMessage(),
'errors' => $this->getValidationErrors()
], 422);
}
}
正しいログ記録の実装方法
効果的なログ記録は問題の追跡と解決に不可欠です。
- 階層的なログレベルの活用
class PaymentService
{
public function processPayment(Order $order)
{
try {
Log::info('決済処理開始', ['order_id' => $order->id]);
$result = $this->gateway->charge($order);
Log::info('決済処理完了', [
'order_id' => $order->id,
'transaction_id' => $result->transaction_id
]);
return $result;
} catch (Exception $e) {
Log::error('決済処理エラー', [
'order_id' => $order->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
}
- カスタムログチャンネルの設定
// config/logging.php
'channels' => [
'payment' => [
'driver' => 'daily',
'path' => storage_path('logs/payment.log'),
'level' => 'debug',
'days' => 14,
],
'security' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Security Bot',
'emoji' => ':warning:',
'level' => 'critical',
],
],
// 使用例
Log::channel('payment')->info('決済処理開始');
Log::channel('security')->critical('不正アクセスを検知');
ユーザーフレンドリーなエラー画面の作成
エラー画面は、ユーザー体験の重要な部分です。
- カスタムエラービューの作成
// resources/views/errors/500.blade.php
@extends('layouts.error')
@section('content')
<div class="error-container">
<h1>申し訳ありません</h1>
<p>{{ $exception->getMessage() ?: 'システムエラーが発生しました' }}</p>
@if(app()->environment('local'))
<div class="debug-info">
<pre>{{ $exception->getTraceAsString() }}</pre>
</div>
@endif
<div class="action-buttons">
<a href="{{ url('/') }}" class="btn">ホームに戻る</a>
<button onclick="window.history.back()" class="btn">前のページに戻る</button>
</div>
</div>
@endsection
- 環境に応じたエラー表示の制御
// app/Exceptions/Handler.php
public function render($request, Throwable $exception)
{
if ($this->shouldReturnJson($request, $exception)) {
return $this->renderJsonError($exception);
}
if (app()->environment('production')) {
return $this->renderProductionError($exception);
}
return parent::render($request, $exception);
}
protected function renderJsonError(Throwable $exception)
{
$status = $this->getHttpStatusCode($exception);
return response()->json([
'error' => $this->getErrorMessage($exception, $status),
'status' => $status
], $status);
}
protected function renderProductionError(Throwable $exception)
{
$status = $this->getHttpStatusCode($exception);
return response()->view('errors.custom', [
'message' => $this->getErrorMessage($exception, $status),
'status' => $status,
'help' => $this->getHelpText($status)
], $status);
}
ベストプラクティスのポイント:
- 例外メッセージは具体的で行動可能な情報を含める
- ログはコンテキスト情報を十分に含め、適切なレベルで記録
- 本番環境では詳細なエラー情報を隠蔽
- ユーザーフレンドリーなエラー画面を提供
- 環境に応じて適切なエラー情報を表示
- セキュリティに配慮したエラーハンドリングを実装
これらのベストプラクティスを適切に実装することで、安全で使いやすいエラー処理システムを構築できます。
Laravel Exception の基礎知識
Laravel におけるエラー処理の重要性
Webアプリケーションの開発において、エラー処理は非常に重要な要素です。特にLaravelのような本番環境で使用されるフレームワークでは、適切なエラー処理は以下の理由から不可欠となります:
- アプリケーションの信頼性向上
- 予期せぬエラーを適切にキャッチし処理
- システムの安定性を確保
- ユーザー体験の維持
- セキュリティの確保
- センシティブな情報の漏洩防止
- エラーメッセージによる脆弱性露出の回避
- 適切なログ記録によるセキュリティ監査の実現
- デバッグの効率化
- 開発環境での詳細なエラー情報の提供
- 本番環境での適切なエラーハンドリング
- 問題の早期発見と解決
例外処理の基本的な仕組み
LaravelのExceptionの仕組みは、PHPの例外処理メカニズムを基盤としながら、フレームワーク固有の機能を追加しています。
try {
// 潜在的にエラーが発生する可能性のあるコード
$result = $this->someRiskyOperation();
} catch (Exception $e) {
// エラーハンドリング
Log::error('エラーが発生しました: ' . $e->getMessage());
return response()->json(['error' => '処理に失敗しました'], 500);
} finally {
// 必ず実行される処理
$this->cleanup();
}
基本的な例外処理の流れ:
- 例外の発生
- 例外のキャッチ
- エラーハンドリング
- レスポンス生成
まさかの例外クラスと障害構造
Laravelには様々な組み込み例外クラスが用意されています:
- 基本的な例外クラス
Exception: すべての例外の基底クラスRuntimeException: 実行時の例外LogicException: プログラムのロジックエラー
- Laravel固有の例外クラス
// モデルが見つからない場合の例外 use Illuminate\Database\Eloquent\ModelNotFoundException; // バリデーション失敗時の例外 use Illuminate\Validation\ValidationException; // 認証・認可の例外 use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Access\AuthorizationException;
- 例外の階層構造
Exception
├── RuntimeException
│ ├── ModelNotFoundException
│ └── ValidationException
└── LogicException
└── AuthorizationException
これらの例外クラスは、それぞれ特定のエラー状況に対応するように設計されており、適切なHTTPステータスコードやエラーメッセージを持っています。
重要なポイント:
- 例外クラスは目的に応じて使い分ける
- カスタム例外は既存の例外クラスを継承して作成
- エラーメッセージは具体的かつ適切な情報を含める
以上が、Laravel Exceptionの基本的な概念と仕組みです。これらの理解は、より高度なエラー処理の実装の基礎となります。
Laravel Exception の実践的な使い方
カスタム例外クラスの作成方法
Laravelでは、アプリケーション固有のエラー状況に対応するために、カスタム例外クラスを作成することができます。
- 基本的なカスタム例外クラスの作成
<?php
namespace App\Exceptions;
use Exception;
class PaymentFailedException extends Exception
{
protected $message = '決済処理に失敗しました';
protected $code = 500;
public function report()
{
// エラーのログ記録
\Log::error('決済エラー: ' . $this->getMessage());
}
public function render($request)
{
// APIリクエストの場合のレスポンス
if ($request->expectsJson()) {
return response()->json([
'error' => $this->getMessage(),
'code' => $this->code
], $this->code);
}
// 通常のWebリクエストの場合のレスポンス
return view('errors.payment', [
'message' => $this->getMessage()
]);
}
}
- コンストラクタでのカスタマイズ
public function __construct($message = null, $code = 0, Exception $previous = null)
{
// カスタムメッセージがある場合は上書き
if (!is_null($message)) {
$this->message = $message;
}
parent::__construct($this->message, $code, $previous);
}
例外のキャッチと処理の実装例
実際のアプリケーションでの例外処理の実装例を見ていきましょう。
- サービスクラスでの使用例
class PaymentService
{
public function processPayment(Order $order)
{
try {
// 決済処理
$result = $this->paymentGateway->charge($order->amount);
if (!$result->isSuccessful()) {
throw new PaymentFailedException(
'決済が拒否されました: ' . $result->getMessage()
);
}
return $result;
} catch (PaymentFailedException $e) {
// アプリケーション固有の例外処理
report($e);
throw $e;
} catch (\Exception $e) {
// その他の予期せぬ例外の処理
report($e);
throw new PaymentFailedException(
'決済処理中に予期せぬエラーが発生しました',
500,
$e
);
}
}
}
- コントローラでの使用例
class OrderController extends Controller
{
public function store(OrderRequest $request)
{
try {
$order = $this->orderService->createOrder($request->validated());
$this->paymentService->processPayment($order);
return redirect()
->route('orders.show', $order)
->with('success', '注文が完了しました');
} catch (PaymentFailedException $e) {
return back()
->withInput()
->withErrors(['payment' => $e->getMessage()]);
}
}
}
HTTPステータスコードとの連携方法
Laravelでは、例外とHTTPステータスコードを適切に連携させることが重要です。
- レスポンストレイトの使用
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiException extends HttpException
{
public function __construct($message = null, $code = Response::HTTP_BAD_REQUEST)
{
parent::__construct($code, $message ?? 'APIエラーが発生しました');
}
}
- ステータスコードのマッピング例
// app/Exceptions/Handler.php
protected $statusCodeMapping = [
ModelNotFoundException::class => 404,
AuthorizationException::class => 403,
ValidationException::class => 422,
PaymentFailedException::class => 400
];
public function render($request, Throwable $e)
{
$statusCode = $this->statusCodeMapping[get_class($e)] ?? 500;
if ($request->expectsJson()) {
return response()->json([
'error' => $e->getMessage(),
'status' => $statusCode
], $statusCode);
}
return parent::render($request, $e);
}
実装のポイント:
- 例外クラスは目的に応じて適切に設計する
- レスポンスフォーマットを統一する
- ログ記録とエラー通知を適切に設定する
- APIとWeb画面で適切なレスポンスを返す
- セキュリティに配慮したエラーメッセージを設定する
これらの実装方法を理解し、適切に活用することで、堅牢なエラー処理システムを構築することができます。
エラー処理のベストプラクティス
意味のある例外メッセージの設計
効果的な例外メッセージは、問題の特定と解決を容易にします。
- メッセージ設計の原則
class OrderException extends Exception
{
public function __construct($orderId, $reason, $suggestion = null)
{
$message = sprintf(
'Order #%d failed: %s',
$orderId,
$reason
);
if ($suggestion) {
$message .= sprintf('. Suggestion: %s', $suggestion);
}
parent::__construct($message);
}
}
// 使用例
throw new OrderException(
$order->id,
'在庫不足',
'少量に分けて注文してください'
);
- コンテキスト情報の付加
class DataValidationException extends Exception
{
protected $errors;
public function __construct(array $errors, $message = '入力データが不正です')
{
$this->errors = $errors;
parent::__construct($message);
}
public function getValidationErrors()
{
return $this->errors;
}
public function render($request)
{
return response()->json([
'message' => $this->getMessage(),
'errors' => $this->getValidationErrors()
], 422);
}
}
正しいログ記録の実装方法
効果的なログ記録は問題の追跡と解決に不可欠です。
- 階層的なログレベルの活用
class PaymentService
{
public function processPayment(Order $order)
{
try {
Log::info('決済処理開始', ['order_id' => $order->id]);
$result = $this->gateway->charge($order);
Log::info('決済処理完了', [
'order_id' => $order->id,
'transaction_id' => $result->transaction_id
]);
return $result;
} catch (Exception $e) {
Log::error('決済処理エラー', [
'order_id' => $order->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
}
- カスタムログチャンネルの設定
// config/logging.php
'channels' => [
'payment' => [
'driver' => 'daily',
'path' => storage_path('logs/payment.log'),
'level' => 'debug',
'days' => 14,
],
'security' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Security Bot',
'emoji' => ':warning:',
'level' => 'critical',
],
],
// 使用例
Log::channel('payment')->info('決済処理開始');
Log::channel('security')->critical('不正アクセスを検知');
ユーザーフレンドリーなエラー画面の作成
エラー画面は、ユーザー体験の重要な部分です。
- カスタムエラービューの作成
// resources/views/errors/500.blade.php
@extends('layouts.error')
@section('content')
<div class="error-container">
<h1>申し訳ありません</h1>
<p>{{ $exception->getMessage() ?: 'システムエラーが発生しました' }}</p>
@if(app()->environment('local'))
<div class="debug-info">
<pre>{{ $exception->getTraceAsString() }}</pre>
</div>
@endif
<div class="action-buttons">
<a href="{{ url('/') }}" class="btn">ホームに戻る</a>
<button onclick="window.history.back()" class="btn">前のページに戻る</button>
</div>
</div>
@endsection
- 環境に応じたエラー表示の制御
// app/Exceptions/Handler.php
public function render($request, Throwable $exception)
{
if ($this->shouldReturnJson($request, $exception)) {
return $this->renderJsonError($exception);
}
if (app()->environment('production')) {
return $this->renderProductionError($exception);
}
return parent::render($request, $exception);
}
protected function renderJsonError(Throwable $exception)
{
$status = $this->getHttpStatusCode($exception);
return response()->json([
'error' => $this->getErrorMessage($exception, $status),
'status' => $status
], $status);
}
protected function renderProductionError(Throwable $exception)
{
$status = $this->getHttpStatusCode($exception);
return response()->view('errors.custom', [
'message' => $this->getErrorMessage($exception, $status),
'status' => $status,
'help' => $this->getHelpText($status)
], $status);
}
ベストプラクティスのポイント:
- 例外メッセージは具体的で行動可能な情報を含める
- ログはコンテキスト情報を十分に含め、適切なレベルで記録
- 本番環境では詳細なエラー情報を隠蔽
- ユーザーフレンドリーなエラー画面を提供
- 環境に応じて適切なエラー情報を表示
- セキュリティに配慮したエラーハンドリングを実装
これらのベストプラクティスを適切に実装することで、安全で使いやすいエラー処理システムを構築できます。
高度な例外処理手法
グローバル例外ハンドラーの活用
Laravelのグローバル例外ハンドラーを活用することで、アプリケーション全体で一貫したエラー処理を実現できます。
- カスタム例外ハンドラーの実装
// app/Exceptions/Handler.php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
protected $dontReport = [
\App\Exceptions\MinorException::class,
];
public function register(): void
{
$this->reportable(function (\App\Exceptions\CustomException $e) {
// カスタム例外の報告ロジック
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
});
$this->renderable(function (\App\Exceptions\ApiException $e, $request) {
// API例外のレンダリングロジック
return response()->json([
'error' => $e->getMessage(),
'code' => $e->getCode()
], $e->getStatusCode());
});
}
protected function shouldReturnJson($request, Throwable $e): bool
{
return $request->expectsJson() ||
$request->is('api/*') ||
$e instanceof ApiException;
}
}
- マクロを使用した拡張
// app/Providers/AppServiceProvider.php
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Response;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Response::macro('error', function ($message, $code = 400) {
return response()->json([
'error' => $message,
'status' => $code
], $code);
});
}
}
APIのエラー対応設計
RESTful APIにおける効果的なエラーハンドリングの実装方法です。
- APIレスポンスの標準化
class ApiResponse
{
public static function error($message, $errors = [], $code = 400)
{
return response()->json([
'status' => 'error',
'message' => $message,
'errors' => $errors,
'timestamp' => now()->toIso8601String(),
'request_id' => request()->id()
], $code);
}
public static function exception(Throwable $e)
{
$debug = config('app.debug') ? [
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->take(3)
] : null;
return response()->json([
'status' => 'error',
'message' => $e->getMessage(),
'code' => $e->getCode(),
'debug' => $debug
], 500);
}
}
- レート制限とエラー処理の統合
// routes/api.php
Route::middleware(['auth:api', 'throttle:60,1'])->group(function () {
Route::get('/users', function () {
try {
// ユーザー取得ロジック
} catch (Throwable $e) {
return ApiResponse::exception($e);
}
})->withoutMiddleware(['throttle']); // 特定ルートで制限解除
});
// カスタムレート制限ハンドラー
RateLimiter::handleException(function ($e) {
return ApiResponse::error('Too Many Requests', [], 429);
});
非同期処理における例外処理
キューやジョブでの例外処理の実装方法です。
- ジョブクラスでの例外処理
class ProcessPayment implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $maxExceptions = 2;
public $timeout = 120;
public function handle()
{
try {
// 支払い処理ロジック
} catch (PaymentException $e) {
$this->fail($e);
}
}
public function failed(Throwable $e)
{
// 失敗時の通知
Notification::route('slack', config('services.slack.webhook'))
->notify(new PaymentFailedNotification($e));
}
public function retryUntil()
{
return now()->addMinutes(30);
}
}
- 非同期処理のエラー監視
// 失敗したジョブの監視
Queue::failing(function (JobFailed $event) {
$job = $event->job;
$exception = $event->exception;
Log::error('ジョブ失敗', [
'job' => get_class($job),
'queue' => $job->getQueue(),
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString()
]);
// 重大なエラーの場合は即時通知
if ($exception instanceof CriticalException) {
Notification::route('slack', config('services.slack.webhook'))
->notify(new CriticalErrorNotification($exception));
}
});
// キューワーカーの監視
Queue::looping(function () {
// メモリ使用量の監視
if (memory_get_usage() > 100 * 1024 * 1024) {
return false; // ワーカーの再起動
}
});
高度な例外処理のポイント:
- グローバルハンドラーで一貫性のある処理を実現
- APIレスポンスは標準化して扱いやすく
- 非同期処理では再試行戦略を適切に設定
- エラー監視と通知の仕組みを整備
- パフォーマンスとリソース使用を考慮
- デバッグ情報は環境に応じて適切に制御
これらの高度な手法を適切に組み合わせることで、堅牢で保守性の高いエラー処理システムを実現できます。
Laravel Exception の基礎知識
Laravel におけるエラー処理の重要性
Webアプリケーションの開発において、エラー処理は非常に重要な要素です。特にLaravelのような本番環境で使用されるフレームワークでは、適切なエラー処理は以下の理由から不可欠となります:
- アプリケーションの信頼性向上
- 予期せぬエラーを適切にキャッチし処理
- システムの安定性を確保
- ユーザー体験の維持
- セキュリティの確保
- センシティブな情報の漏洩防止
- エラーメッセージによる脆弱性露出の回避
- 適切なログ記録によるセキュリティ監査の実現
- デバッグの効率化
- 開発環境での詳細なエラー情報の提供
- 本番環境での適切なエラーハンドリング
- 問題の早期発見と解決
例外処理の基本的な仕組み
LaravelのExceptionの仕組みは、PHPの例外処理メカニズムを基盤としながら、フレームワーク固有の機能を追加しています。
try {
// 潜在的にエラーが発生する可能性のあるコード
$result = $this->someRiskyOperation();
} catch (Exception $e) {
// エラーハンドリング
Log::error('エラーが発生しました: ' . $e->getMessage());
return response()->json(['error' => '処理に失敗しました'], 500);
} finally {
// 必ず実行される処理
$this->cleanup();
}
基本的な例外処理の流れ:
- 例外の発生
- 例外のキャッチ
- エラーハンドリング
- レスポンス生成
まさかの例外クラスと障害構造
Laravelには様々な組み込み例外クラスが用意されています:
- 基本的な例外クラス
Exception: すべての例外の基底クラスRuntimeException: 実行時の例外LogicException: プログラムのロジックエラー
- Laravel固有の例外クラス
// モデルが見つからない場合の例外 use Illuminate\Database\Eloquent\ModelNotFoundException; // バリデーション失敗時の例外 use Illuminate\Validation\ValidationException; // 認証・認可の例外 use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Access\AuthorizationException;
- 例外の階層構造
Exception
├── RuntimeException
│ ├── ModelNotFoundException
│ └── ValidationException
└── LogicException
└── AuthorizationException
これらの例外クラスは、それぞれ特定のエラー状況に対応するように設計されており、適切なHTTPステータスコードやエラーメッセージを持っています。
重要なポイント:
- 例外クラスは目的に応じて使い分ける
- カスタム例外は既存の例外クラスを継承して作成
- エラーメッセージは具体的かつ適切な情報を含める
以上が、Laravel Exceptionの基本的な概念と仕組みです。これらの理解は、より高度なエラー処理の実装の基礎となります。
Laravel Exception の実践的な使い方
カスタム例外クラスの作成方法
Laravelでは、アプリケーション固有のエラー状況に対応するために、カスタム例外クラスを作成することができます。
- 基本的なカスタム例外クラスの作成
<?php
namespace App\Exceptions;
use Exception;
class PaymentFailedException extends Exception
{
protected $message = '決済処理に失敗しました';
protected $code = 500;
public function report()
{
// エラーのログ記録
\Log::error('決済エラー: ' . $this->getMessage());
}
public function render($request)
{
// APIリクエストの場合のレスポンス
if ($request->expectsJson()) {
return response()->json([
'error' => $this->getMessage(),
'code' => $this->code
], $this->code);
}
// 通常のWebリクエストの場合のレスポンス
return view('errors.payment', [
'message' => $this->getMessage()
]);
}
}
- コンストラクタでのカスタマイズ
public function __construct($message = null, $code = 0, Exception $previous = null)
{
// カスタムメッセージがある場合は上書き
if (!is_null($message)) {
$this->message = $message;
}
parent::__construct($this->message, $code, $previous);
}
例外のキャッチと処理の実装例
実際のアプリケーションでの例外処理の実装例を見ていきましょう。
- サービスクラスでの使用例
class PaymentService
{
public function processPayment(Order $order)
{
try {
// 決済処理
$result = $this->paymentGateway->charge($order->amount);
if (!$result->isSuccessful()) {
throw new PaymentFailedException(
'決済が拒否されました: ' . $result->getMessage()
);
}
return $result;
} catch (PaymentFailedException $e) {
// アプリケーション固有の例外処理
report($e);
throw $e;
} catch (\Exception $e) {
// その他の予期せぬ例外の処理
report($e);
throw new PaymentFailedException(
'決済処理中に予期せぬエラーが発生しました',
500,
$e
);
}
}
}
- コントローラでの使用例
class OrderController extends Controller
{
public function store(OrderRequest $request)
{
try {
$order = $this->orderService->createOrder($request->validated());
$this->paymentService->processPayment($order);
return redirect()
->route('orders.show', $order)
->with('success', '注文が完了しました');
} catch (PaymentFailedException $e) {
return back()
->withInput()
->withErrors(['payment' => $e->getMessage()]);
}
}
}
HTTPステータスコードとの連携方法
Laravelでは、例外とHTTPステータスコードを適切に連携させることが重要です。
- レスポンストレイトの使用
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiException extends HttpException
{
public function __construct($message = null, $code = Response::HTTP_BAD_REQUEST)
{
parent::__construct($code, $message ?? 'APIエラーが発生しました');
}
}
- ステータスコードのマッピング例
// app/Exceptions/Handler.php
protected $statusCodeMapping = [
ModelNotFoundException::class => 404,
AuthorizationException::class => 403,
ValidationException::class => 422,
PaymentFailedException::class => 400
];
public function render($request, Throwable $e)
{
$statusCode = $this->statusCodeMapping[get_class($e)] ?? 500;
if ($request->expectsJson()) {
return response()->json([
'error' => $e->getMessage(),
'status' => $statusCode
], $statusCode);
}
return parent::render($request, $e);
}
実装のポイント:
- 例外クラスは目的に応じて適切に設計する
- レスポンスフォーマットを統一する
- ログ記録とエラー通知を適切に設定する
- APIとWeb画面で適切なレスポンスを返す
- セキュリティに配慮したエラーメッセージを設定する
これらの実装方法を理解し、適切に活用することで、堅牢なエラー処理システムを構築することができます。
エラー処理のベストプラクティス
意味のある例外メッセージの設計
効果的な例外メッセージは、問題の特定と解決を容易にします。
- メッセージ設計の原則
class OrderException extends Exception
{
public function __construct($orderId, $reason, $suggestion = null)
{
$message = sprintf(
'Order #%d failed: %s',
$orderId,
$reason
);
if ($suggestion) {
$message .= sprintf('. Suggestion: %s', $suggestion);
}
parent::__construct($message);
}
}
// 使用例
throw new OrderException(
$order->id,
'在庫不足',
'少量に分けて注文してください'
);
- コンテキスト情報の付加
class DataValidationException extends Exception
{
protected $errors;
public function __construct(array $errors, $message = '入力データが不正です')
{
$this->errors = $errors;
parent::__construct($message);
}
public function getValidationErrors()
{
return $this->errors;
}
public function render($request)
{
return response()->json([
'message' => $this->getMessage(),
'errors' => $this->getValidationErrors()
], 422);
}
}
正しいログ記録の実装方法
効果的なログ記録は問題の追跡と解決に不可欠です。
- 階層的なログレベルの活用
class PaymentService
{
public function processPayment(Order $order)
{
try {
Log::info('決済処理開始', ['order_id' => $order->id]);
$result = $this->gateway->charge($order);
Log::info('決済処理完了', [
'order_id' => $order->id,
'transaction_id' => $result->transaction_id
]);
return $result;
} catch (Exception $e) {
Log::error('決済処理エラー', [
'order_id' => $order->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
}
- カスタムログチャンネルの設定
// config/logging.php
'channels' => [
'payment' => [
'driver' => 'daily',
'path' => storage_path('logs/payment.log'),
'level' => 'debug',
'days' => 14,
],
'security' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Security Bot',
'emoji' => ':warning:',
'level' => 'critical',
],
],
// 使用例
Log::channel('payment')->info('決済処理開始');
Log::channel('security')->critical('不正アクセスを検知');
ユーザーフレンドリーなエラー画面の作成
エラー画面は、ユーザー体験の重要な部分です。
- カスタムエラービューの作成
// resources/views/errors/500.blade.php
@extends('layouts.error')
@section('content')
<div class="error-container">
<h1>申し訳ありません</h1>
<p>{{ $exception->getMessage() ?: 'システムエラーが発生しました' }}</p>
@if(app()->environment('local'))
<div class="debug-info">
<pre>{{ $exception->getTraceAsString() }}</pre>
</div>
@endif
<div class="action-buttons">
<a href="{{ url('/') }}" class="btn">ホームに戻る</a>
<button onclick="window.history.back()" class="btn">前のページに戻る</button>
</div>
</div>
@endsection
- 環境に応じたエラー表示の制御
// app/Exceptions/Handler.php
public function render($request, Throwable $exception)
{
if ($this->shouldReturnJson($request, $exception)) {
return $this->renderJsonError($exception);
}
if (app()->environment('production')) {
return $this->renderProductionError($exception);
}
return parent::render($request, $exception);
}
protected function renderJsonError(Throwable $exception)
{
$status = $this->getHttpStatusCode($exception);
return response()->json([
'error' => $this->getErrorMessage($exception, $status),
'status' => $status
], $status);
}
protected function renderProductionError(Throwable $exception)
{
$status = $this->getHttpStatusCode($exception);
return response()->view('errors.custom', [
'message' => $this->getErrorMessage($exception, $status),
'status' => $status,
'help' => $this->getHelpText($status)
], $status);
}
ベストプラクティスのポイント:
- 例外メッセージは具体的で行動可能な情報を含める
- ログはコンテキスト情報を十分に含め、適切なレベルで記録
- 本番環境では詳細なエラー情報を隠蔽
- ユーザーフレンドリーなエラー画面を提供
- 環境に応じて適切なエラー情報を表示
- セキュリティに配慮したエラーハンドリングを実装
これらのベストプラクティスを適切に実装することで、安全で使いやすいエラー処理システムを構築できます。
高度な例外処理手法
グローバル例外ハンドラーの活用
Laravelのグローバル例外ハンドラーを活用することで、アプリケーション全体で一貫したエラー処理を実現できます。
- カスタム例外ハンドラーの実装
// app/Exceptions/Handler.php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
protected $dontReport = [
\App\Exceptions\MinorException::class,
];
public function register(): void
{
$this->reportable(function (\App\Exceptions\CustomException $e) {
// カスタム例外の報告ロジック
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
});
$this->renderable(function (\App\Exceptions\ApiException $e, $request) {
// API例外のレンダリングロジック
return response()->json([
'error' => $e->getMessage(),
'code' => $e->getCode()
], $e->getStatusCode());
});
}
protected function shouldReturnJson($request, Throwable $e): bool
{
return $request->expectsJson() ||
$request->is('api/*') ||
$e instanceof ApiException;
}
}
- マクロを使用した拡張
// app/Providers/AppServiceProvider.php
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Response;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Response::macro('error', function ($message, $code = 400) {
return response()->json([
'error' => $message,
'status' => $code
], $code);
});
}
}
APIのエラー対応設計
RESTful APIにおける効果的なエラーハンドリングの実装方法です。
- APIレスポンスの標準化
class ApiResponse
{
public static function error($message, $errors = [], $code = 400)
{
return response()->json([
'status' => 'error',
'message' => $message,
'errors' => $errors,
'timestamp' => now()->toIso8601String(),
'request_id' => request()->id()
], $code);
}
public static function exception(Throwable $e)
{
$debug = config('app.debug') ? [
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->take(3)
] : null;
return response()->json([
'status' => 'error',
'message' => $e->getMessage(),
'code' => $e->getCode(),
'debug' => $debug
], 500);
}
}
- レート制限とエラー処理の統合
// routes/api.php
Route::middleware(['auth:api', 'throttle:60,1'])->group(function () {
Route::get('/users', function () {
try {
// ユーザー取得ロジック
} catch (Throwable $e) {
return ApiResponse::exception($e);
}
})->withoutMiddleware(['throttle']); // 特定ルートで制限解除
});
// カスタムレート制限ハンドラー
RateLimiter::handleException(function ($e) {
return ApiResponse::error('Too Many Requests', [], 429);
});
非同期処理における例外処理
キューやジョブでの例外処理の実装方法です。
- ジョブクラスでの例外処理
class ProcessPayment implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $maxExceptions = 2;
public $timeout = 120;
public function handle()
{
try {
// 支払い処理ロジック
} catch (PaymentException $e) {
$this->fail($e);
}
}
public function failed(Throwable $e)
{
// 失敗時の通知
Notification::route('slack', config('services.slack.webhook'))
->notify(new PaymentFailedNotification($e));
}
public function retryUntil()
{
return now()->addMinutes(30);
}
}
- 非同期処理のエラー監視
// 失敗したジョブの監視
Queue::failing(function (JobFailed $event) {
$job = $event->job;
$exception = $event->exception;
Log::error('ジョブ失敗', [
'job' => get_class($job),
'queue' => $job->getQueue(),
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString()
]);
// 重大なエラーの場合は即時通知
if ($exception instanceof CriticalException) {
Notification::route('slack', config('services.slack.webhook'))
->notify(new CriticalErrorNotification($exception));
}
});
// キューワーカーの監視
Queue::looping(function () {
// メモリ使用量の監視
if (memory_get_usage() > 100 * 1024 * 1024) {
return false; // ワーカーの再起動
}
});
高度な例外処理のポイント:
- グローバルハンドラーで一貫性のある処理を実現
- APIレスポンスは標準化して扱いやすく
- 非同期処理では再試行戦略を適切に設定
- エラー監視と通知の仕組みを整備
- パフォーマンスとリソース使用を考慮
- デバッグ情報は環境に応じて適切に制御
これらの高度な手法を適切に組み合わせることで、堅牢で保守性の高いエラー処理システムを実現できます。
デバッグとトラブルシューティング
効率的なデバッグ手法
Laravelにおける効果的なデバッグ方法を紹介します。
- デバッグツールの活用
// デバッグバーの設定
// config/debugbar.php
return [
'enabled' => env('DEBUGBAR_ENABLED', false),
'collectors' => [
'phpinfo' => true,
'messages' => true,
'time' => true,
'memory' => true,
'exceptions' => true,
'log' => true,
'db' => true,
'views' => true,
'route' => true,
'cache' => true,
],
];
// 使用例
\Debugbar::info('デバッグ情報');
\Debugbar::error('エラー情報');
\Debugbar::startMeasure('operation', '処理の計測');
\Debugbar::stopMeasure('operation');
- 例外のデバッグ補助メソッド
class CustomException extends Exception
{
public function context()
{
return [
'request_id' => request()->id(),
'user_id' => auth()->id(),
'url' => request()->fullUrl(),
'input' => request()->except(['password']),
'headers' => request()->headers->all(),
'session' => session()->all()
];
}
public function getDebugData()
{
if (!app()->isLocal()) {
return null;
}
return [
'file' => $this->getFile(),
'line' => $this->getLine(),
'trace' => $this->getTraceAsString(),
'previous' => $this->getPrevious() ? [
'message' => $this->getPrevious()->getMessage(),
'class' => get_class($this->getPrevious())
] : null
];
}
}
一般的な例外パターンと解決策
よくある例外パターンとその対処方法です。
- データベース関連の例外
try {
$user = User::findOrFail($id);
} catch (ModelNotFoundException $e) {
Log::error('ユーザーが見つかりません', [
'id' => $id,
'trace' => $e->getTraceAsString()
]);
return $this->handleModelNotFound($e);
}
protected function handleModelNotFound($e)
{
if (request()->expectsJson()) {
return response()->json([
'error' => 'リソースが見つかりません',
'details' => [
'model' => class_basename($e->getModel()),
'id' => $e->getIds()
]
], 404);
}
return redirect()->back()
->withErrors(['message' => 'リソースが見つかりません']);
}
- 認証/認可の例外
class AuthExceptionHandler
{
public function handle($e)
{
if ($e instanceof AuthenticationException) {
return $this->handleUnauthenticated($e);
}
if ($e instanceof AuthorizationException) {
return $this->handleUnauthorized($e);
}
return null;
}
protected function handleUnauthenticated($e)
{
Log::warning('未認証アクセス', [
'ip' => request()->ip(),
'url' => request()->fullUrl()
]);
return response()->json([
'error' => '認証が必要です',
'login_url' => route('login')
], 401);
}
protected function handleUnauthorized($e)
{
Log::warning('不正なアクセス試行', [
'user' => auth()->user()->id,
'action' => request()->route()->getName()
]);
return response()->json([
'error' => 'アクセス権限がありません'
], 403);
}
}
本番環境での例外監視方法
本番環境における効果的な例外監視の実装方法です。
- モニタリングシステムの統合
// app/Providers/AppServiceProvider.php
public function boot()
{
// Sentryの統合
if (app()->bound('sentry')) {
$this->app->make('sentry')->beforeSend(function ($event) {
if (app()->environment('production')) {
// 機密情報の削除
unset($event['request']['cookies']);
unset($event['request']['headers']['authorization']);
}
return $event;
});
}
}
// 使用例
try {
// 危険な操作
} catch (Exception $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
throw $e;
}
- カスタムモニタリング実装
class ExceptionMonitor
{
protected $threshold = 10;
protected $timeWindow = 300; // 5分
public function handleException($e)
{
$key = $this->getExceptionKey($e);
// Redisを使用した例外発生回数の追跡
$count = Redis::incr($key);
Redis::expire($key, $this->timeWindow);
if ($count >= $this->threshold) {
$this->notifyHighFrequencyException($e, $count);
Redis::del($key);
}
}
protected function getExceptionKey($e)
{
return 'exception:' . get_class($e) . ':' . date('YmdHi');
}
protected function notifyHighFrequencyException($e, $count)
{
Notification::route('slack', config('services.slack.webhook'))
->notify(new HighFrequencyExceptionNotification($e, $count));
}
}
デバッグとトラブルシューティングのポイント:
- デバッグツールを効果的に活用する
- コンテキスト情報を十分に収集する
- 環境に応じて適切なデバッグ情報を提供する
- 一般的な例外パターンに対する標準的な対処方法を用意
- 本番環境での監視体制を整備する
- セキュリティに配慮した情報収集を行う
これらの手法を適切に活用することで、効率的なデバッグと問題解決が可能になります。
Laravel Exception の基礎知識
Laravel におけるエラー処理の重要性
Webアプリケーションの開発において、エラー処理は非常に重要な要素です。特にLaravelのような本番環境で使用されるフレームワークでは、適切なエラー処理は以下の理由から不可欠となります:
- アプリケーションの信頼性向上
- 予期せぬエラーを適切にキャッチし処理
- システムの安定性を確保
- ユーザー体験の維持
- セキュリティの確保
- センシティブな情報の漏洩防止
- エラーメッセージによる脆弱性露出の回避
- 適切なログ記録によるセキュリティ監査の実現
- デバッグの効率化
- 開発環境での詳細なエラー情報の提供
- 本番環境での適切なエラーハンドリング
- 問題の早期発見と解決
例外処理の基本的な仕組み
LaravelのExceptionの仕組みは、PHPの例外処理メカニズムを基盤としながら、フレームワーク固有の機能を追加しています。
try {
// 潜在的にエラーが発生する可能性のあるコード
$result = $this->someRiskyOperation();
} catch (Exception $e) {
// エラーハンドリング
Log::error('エラーが発生しました: ' . $e->getMessage());
return response()->json(['error' => '処理に失敗しました'], 500);
} finally {
// 必ず実行される処理
$this->cleanup();
}
基本的な例外処理の流れ:
- 例外の発生
- 例外のキャッチ
- エラーハンドリング
- レスポンス生成
まさかの例外クラスと障害構造
Laravelには様々な組み込み例外クラスが用意されています:
- 基本的な例外クラス
Exception: すべての例外の基底クラスRuntimeException: 実行時の例外LogicException: プログラムのロジックエラー
- Laravel固有の例外クラス
// モデルが見つからない場合の例外 use Illuminate\Database\Eloquent\ModelNotFoundException; // バリデーション失敗時の例外 use Illuminate\Validation\ValidationException; // 認証・認可の例外 use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Access\AuthorizationException;
- 例外の階層構造
Exception
├── RuntimeException
│ ├── ModelNotFoundException
│ └── ValidationException
└── LogicException
└── AuthorizationException
これらの例外クラスは、それぞれ特定のエラー状況に対応するように設計されており、適切なHTTPステータスコードやエラーメッセージを持っています。
重要なポイント:
- 例外クラスは目的に応じて使い分ける
- カスタム例外は既存の例外クラスを継承して作成
- エラーメッセージは具体的かつ適切な情報を含める
以上が、Laravel Exceptionの基本的な概念と仕組みです。これらの理解は、より高度なエラー処理の実装の基礎となります。
Laravel Exception の実践的な使い方
カスタム例外クラスの作成方法
Laravelでは、アプリケーション固有のエラー状況に対応するために、カスタム例外クラスを作成することができます。
- 基本的なカスタム例外クラスの作成
<?php
namespace App\Exceptions;
use Exception;
class PaymentFailedException extends Exception
{
protected $message = '決済処理に失敗しました';
protected $code = 500;
public function report()
{
// エラーのログ記録
\Log::error('決済エラー: ' . $this->getMessage());
}
public function render($request)
{
// APIリクエストの場合のレスポンス
if ($request->expectsJson()) {
return response()->json([
'error' => $this->getMessage(),
'code' => $this->code
], $this->code);
}
// 通常のWebリクエストの場合のレスポンス
return view('errors.payment', [
'message' => $this->getMessage()
]);
}
}
- コンストラクタでのカスタマイズ
public function __construct($message = null, $code = 0, Exception $previous = null)
{
// カスタムメッセージがある場合は上書き
if (!is_null($message)) {
$this->message = $message;
}
parent::__construct($this->message, $code, $previous);
}
例外のキャッチと処理の実装例
実際のアプリケーションでの例外処理の実装例を見ていきましょう。
- サービスクラスでの使用例
class PaymentService
{
public function processPayment(Order $order)
{
try {
// 決済処理
$result = $this->paymentGateway->charge($order->amount);
if (!$result->isSuccessful()) {
throw new PaymentFailedException(
'決済が拒否されました: ' . $result->getMessage()
);
}
return $result;
} catch (PaymentFailedException $e) {
// アプリケーション固有の例外処理
report($e);
throw $e;
} catch (\Exception $e) {
// その他の予期せぬ例外の処理
report($e);
throw new PaymentFailedException(
'決済処理中に予期せぬエラーが発生しました',
500,
$e
);
}
}
}
- コントローラでの使用例
class OrderController extends Controller
{
public function store(OrderRequest $request)
{
try {
$order = $this->orderService->createOrder($request->validated());
$this->paymentService->processPayment($order);
return redirect()
->route('orders.show', $order)
->with('success', '注文が完了しました');
} catch (PaymentFailedException $e) {
return back()
->withInput()
->withErrors(['payment' => $e->getMessage()]);
}
}
}
HTTPステータスコードとの連携方法
Laravelでは、例外とHTTPステータスコードを適切に連携させることが重要です。
- レスポンストレイトの使用
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiException extends HttpException
{
public function __construct($message = null, $code = Response::HTTP_BAD_REQUEST)
{
parent::__construct($code, $message ?? 'APIエラーが発生しました');
}
}
- ステータスコードのマッピング例
// app/Exceptions/Handler.php
protected $statusCodeMapping = [
ModelNotFoundException::class => 404,
AuthorizationException::class => 403,
ValidationException::class => 422,
PaymentFailedException::class => 400
];
public function render($request, Throwable $e)
{
$statusCode = $this->statusCodeMapping[get_class($e)] ?? 500;
if ($request->expectsJson()) {
return response()->json([
'error' => $e->getMessage(),
'status' => $statusCode
], $statusCode);
}
return parent::render($request, $e);
}
実装のポイント:
- 例外クラスは目的に応じて適切に設計する
- レスポンスフォーマットを統一する
- ログ記録とエラー通知を適切に設定する
- APIとWeb画面で適切なレスポンスを返す
- セキュリティに配慮したエラーメッセージを設定する
これらの実装方法を理解し、適切に活用することで、堅牢なエラー処理システムを構築することができます。
エラー処理のベストプラクティス
意味のある例外メッセージの設計
効果的な例外メッセージは、問題の特定と解決を容易にします。
- メッセージ設計の原則
class OrderException extends Exception
{
public function __construct($orderId, $reason, $suggestion = null)
{
$message = sprintf(
'Order #%d failed: %s',
$orderId,
$reason
);
if ($suggestion) {
$message .= sprintf('. Suggestion: %s', $suggestion);
}
parent::__construct($message);
}
}
// 使用例
throw new OrderException(
$order->id,
'在庫不足',
'少量に分けて注文してください'
);
- コンテキスト情報の付加
class DataValidationException extends Exception
{
protected $errors;
public function __construct(array $errors, $message = '入力データが不正です')
{
$this->errors = $errors;
parent::__construct($message);
}
public function getValidationErrors()
{
return $this->errors;
}
public function render($request)
{
return response()->json([
'message' => $this->getMessage(),
'errors' => $this->getValidationErrors()
], 422);
}
}
正しいログ記録の実装方法
効果的なログ記録は問題の追跡と解決に不可欠です。
- 階層的なログレベルの活用
class PaymentService
{
public function processPayment(Order $order)
{
try {
Log::info('決済処理開始', ['order_id' => $order->id]);
$result = $this->gateway->charge($order);
Log::info('決済処理完了', [
'order_id' => $order->id,
'transaction_id' => $result->transaction_id
]);
return $result;
} catch (Exception $e) {
Log::error('決済処理エラー', [
'order_id' => $order->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
}
- カスタムログチャンネルの設定
// config/logging.php
'channels' => [
'payment' => [
'driver' => 'daily',
'path' => storage_path('logs/payment.log'),
'level' => 'debug',
'days' => 14,
],
'security' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Security Bot',
'emoji' => ':warning:',
'level' => 'critical',
],
],
// 使用例
Log::channel('payment')->info('決済処理開始');
Log::channel('security')->critical('不正アクセスを検知');
ユーザーフレンドリーなエラー画面の作成
エラー画面は、ユーザー体験の重要な部分です。
- カスタムエラービューの作成
// resources/views/errors/500.blade.php
@extends('layouts.error')
@section('content')
<div class="error-container">
<h1>申し訳ありません</h1>
<p>{{ $exception->getMessage() ?: 'システムエラーが発生しました' }}</p>
@if(app()->environment('local'))
<div class="debug-info">
<pre>{{ $exception->getTraceAsString() }}</pre>
</div>
@endif
<div class="action-buttons">
<a href="{{ url('/') }}" class="btn">ホームに戻る</a>
<button onclick="window.history.back()" class="btn">前のページに戻る</button>
</div>
</div>
@endsection
- 環境に応じたエラー表示の制御
// app/Exceptions/Handler.php
public function render($request, Throwable $exception)
{
if ($this->shouldReturnJson($request, $exception)) {
return $this->renderJsonError($exception);
}
if (app()->environment('production')) {
return $this->renderProductionError($exception);
}
return parent::render($request, $exception);
}
protected function renderJsonError(Throwable $exception)
{
$status = $this->getHttpStatusCode($exception);
return response()->json([
'error' => $this->getErrorMessage($exception, $status),
'status' => $status
], $status);
}
protected function renderProductionError(Throwable $exception)
{
$status = $this->getHttpStatusCode($exception);
return response()->view('errors.custom', [
'message' => $this->getErrorMessage($exception, $status),
'status' => $status,
'help' => $this->getHelpText($status)
], $status);
}
ベストプラクティスのポイント:
- 例外メッセージは具体的で行動可能な情報を含める
- ログはコンテキスト情報を十分に含め、適切なレベルで記録
- 本番環境では詳細なエラー情報を隠蔽
- ユーザーフレンドリーなエラー画面を提供
- 環境に応じて適切なエラー情報を表示
- セキュリティに配慮したエラーハンドリングを実装
これらのベストプラクティスを適切に実装することで、安全で使いやすいエラー処理システムを構築できます。
高度な例外処理手法
グローバル例外ハンドラーの活用
Laravelのグローバル例外ハンドラーを活用することで、アプリケーション全体で一貫したエラー処理を実現できます。
- カスタム例外ハンドラーの実装
// app/Exceptions/Handler.php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
protected $dontReport = [
\App\Exceptions\MinorException::class,
];
public function register(): void
{
$this->reportable(function (\App\Exceptions\CustomException $e) {
// カスタム例外の報告ロジック
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
});
$this->renderable(function (\App\Exceptions\ApiException $e, $request) {
// API例外のレンダリングロジック
return response()->json([
'error' => $e->getMessage(),
'code' => $e->getCode()
], $e->getStatusCode());
});
}
protected function shouldReturnJson($request, Throwable $e): bool
{
return $request->expectsJson() ||
$request->is('api/*') ||
$e instanceof ApiException;
}
}
- マクロを使用した拡張
// app/Providers/AppServiceProvider.php
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Response;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Response::macro('error', function ($message, $code = 400) {
return response()->json([
'error' => $message,
'status' => $code
], $code);
});
}
}
APIのエラー対応設計
RESTful APIにおける効果的なエラーハンドリングの実装方法です。
- APIレスポンスの標準化
class ApiResponse
{
public static function error($message, $errors = [], $code = 400)
{
return response()->json([
'status' => 'error',
'message' => $message,
'errors' => $errors,
'timestamp' => now()->toIso8601String(),
'request_id' => request()->id()
], $code);
}
public static function exception(Throwable $e)
{
$debug = config('app.debug') ? [
'exception' => get_class($e),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => collect($e->getTrace())->take(3)
] : null;
return response()->json([
'status' => 'error',
'message' => $e->getMessage(),
'code' => $e->getCode(),
'debug' => $debug
], 500);
}
}
- レート制限とエラー処理の統合
// routes/api.php
Route::middleware(['auth:api', 'throttle:60,1'])->group(function () {
Route::get('/users', function () {
try {
// ユーザー取得ロジック
} catch (Throwable $e) {
return ApiResponse::exception($e);
}
})->withoutMiddleware(['throttle']); // 特定ルートで制限解除
});
// カスタムレート制限ハンドラー
RateLimiter::handleException(function ($e) {
return ApiResponse::error('Too Many Requests', [], 429);
});
非同期処理における例外処理
キューやジョブでの例外処理の実装方法です。
- ジョブクラスでの例外処理
class ProcessPayment implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $maxExceptions = 2;
public $timeout = 120;
public function handle()
{
try {
// 支払い処理ロジック
} catch (PaymentException $e) {
$this->fail($e);
}
}
public function failed(Throwable $e)
{
// 失敗時の通知
Notification::route('slack', config('services.slack.webhook'))
->notify(new PaymentFailedNotification($e));
}
public function retryUntil()
{
return now()->addMinutes(30);
}
}
- 非同期処理のエラー監視
// 失敗したジョブの監視
Queue::failing(function (JobFailed $event) {
$job = $event->job;
$exception = $event->exception;
Log::error('ジョブ失敗', [
'job' => get_class($job),
'queue' => $job->getQueue(),
'error' => $exception->getMessage(),
'trace' => $exception->getTraceAsString()
]);
// 重大なエラーの場合は即時通知
if ($exception instanceof CriticalException) {
Notification::route('slack', config('services.slack.webhook'))
->notify(new CriticalErrorNotification($exception));
}
});
// キューワーカーの監視
Queue::looping(function () {
// メモリ使用量の監視
if (memory_get_usage() > 100 * 1024 * 1024) {
return false; // ワーカーの再起動
}
});
高度な例外処理のポイント:
- グローバルハンドラーで一貫性のある処理を実現
- APIレスポンスは標準化して扱いやすく
- 非同期処理では再試行戦略を適切に設定
- エラー監視と通知の仕組みを整備
- パフォーマンスとリソース使用を考慮
- デバッグ情報は環境に応じて適切に制御
これらの高度な手法を適切に組み合わせることで、堅牢で保守性の高いエラー処理システムを実現できます。
デバッグとトラブルシューティング
効率的なデバッグ手法
Laravelにおける効果的なデバッグ方法を紹介します。
- デバッグツールの活用
// デバッグバーの設定
// config/debugbar.php
return [
'enabled' => env('DEBUGBAR_ENABLED', false),
'collectors' => [
'phpinfo' => true,
'messages' => true,
'time' => true,
'memory' => true,
'exceptions' => true,
'log' => true,
'db' => true,
'views' => true,
'route' => true,
'cache' => true,
],
];
// 使用例
\Debugbar::info('デバッグ情報');
\Debugbar::error('エラー情報');
\Debugbar::startMeasure('operation', '処理の計測');
\Debugbar::stopMeasure('operation');
- 例外のデバッグ補助メソッド
class CustomException extends Exception
{
public function context()
{
return [
'request_id' => request()->id(),
'user_id' => auth()->id(),
'url' => request()->fullUrl(),
'input' => request()->except(['password']),
'headers' => request()->headers->all(),
'session' => session()->all()
];
}
public function getDebugData()
{
if (!app()->isLocal()) {
return null;
}
return [
'file' => $this->getFile(),
'line' => $this->getLine(),
'trace' => $this->getTraceAsString(),
'previous' => $this->getPrevious() ? [
'message' => $this->getPrevious()->getMessage(),
'class' => get_class($this->getPrevious())
] : null
];
}
}
一般的な例外パターンと解決策
よくある例外パターンとその対処方法です。
- データベース関連の例外
try {
$user = User::findOrFail($id);
} catch (ModelNotFoundException $e) {
Log::error('ユーザーが見つかりません', [
'id' => $id,
'trace' => $e->getTraceAsString()
]);
return $this->handleModelNotFound($e);
}
protected function handleModelNotFound($e)
{
if (request()->expectsJson()) {
return response()->json([
'error' => 'リソースが見つかりません',
'details' => [
'model' => class_basename($e->getModel()),
'id' => $e->getIds()
]
], 404);
}
return redirect()->back()
->withErrors(['message' => 'リソースが見つかりません']);
}
- 認証/認可の例外
class AuthExceptionHandler
{
public function handle($e)
{
if ($e instanceof AuthenticationException) {
return $this->handleUnauthenticated($e);
}
if ($e instanceof AuthorizationException) {
return $this->handleUnauthorized($e);
}
return null;
}
protected function handleUnauthenticated($e)
{
Log::warning('未認証アクセス', [
'ip' => request()->ip(),
'url' => request()->fullUrl()
]);
return response()->json([
'error' => '認証が必要です',
'login_url' => route('login')
], 401);
}
protected function handleUnauthorized($e)
{
Log::warning('不正なアクセス試行', [
'user' => auth()->user()->id,
'action' => request()->route()->getName()
]);
return response()->json([
'error' => 'アクセス権限がありません'
], 403);
}
}
本番環境での例外監視方法
本番環境における効果的な例外監視の実装方法です。
- モニタリングシステムの統合
// app/Providers/AppServiceProvider.php
public function boot()
{
// Sentryの統合
if (app()->bound('sentry')) {
$this->app->make('sentry')->beforeSend(function ($event) {
if (app()->environment('production')) {
// 機密情報の削除
unset($event['request']['cookies']);
unset($event['request']['headers']['authorization']);
}
return $event;
});
}
}
// 使用例
try {
// 危険な操作
} catch (Exception $e) {
if (app()->bound('sentry')) {
app('sentry')->captureException($e);
}
throw $e;
}
- カスタムモニタリング実装
class ExceptionMonitor
{
protected $threshold = 10;
protected $timeWindow = 300; // 5分
public function handleException($e)
{
$key = $this->getExceptionKey($e);
// Redisを使用した例外発生回数の追跡
$count = Redis::incr($key);
Redis::expire($key, $this->timeWindow);
if ($count >= $this->threshold) {
$this->notifyHighFrequencyException($e, $count);
Redis::del($key);
}
}
protected function getExceptionKey($e)
{
return 'exception:' . get_class($e) . ':' . date('YmdHi');
}
protected function notifyHighFrequencyException($e, $count)
{
Notification::route('slack', config('services.slack.webhook'))
->notify(new HighFrequencyExceptionNotification($e, $count));
}
}
デバッグとトラブルシューティングのポイント:
- デバッグツールを効果的に活用する
- コンテキスト情報を十分に収集する
- 環境に応じて適切なデバッグ情報を提供する
- 一般的な例外パターンに対する標準的な対処方法を用意
- 本番環境での監視体制を整備する
- セキュリティに配慮した情報収集を行う
これらの手法を適切に活用することで、効率的なデバッグと問題解決が可能になります。
Laravel 例外のテスト
例外処理のユニットテスト作成
例外処理の信頼性を確保するためのテスト手法を解説します。
- 基本的な例外テスト
class PaymentServiceTest extends TestCase
{
/**
* @test
* @expectedException App\Exceptions\PaymentFailedException
*/
public function process_payment_throws_exception_on_failure()
{
$service = new PaymentService();
$this->expectException(PaymentFailedException::class);
$this->expectExceptionMessage('決済処理に失敗しました');
$service->processPayment([
'amount' => 1000,
'card_number' => '1234-5678-9012-3456'
]);
}
/** @test */
public function handler_returns_correct_status_code()
{
$exception = new PaymentFailedException('決済エラー');
$request = Request::create('/api/payment', 'POST');
$response = app(Handler::class)->render($request, $exception);
$this->assertEquals(400, $response->getStatusCode());
$this->assertJsonStringEqualsJsonString(
json_encode(['error' => '決済エラー']),
$response->getContent()
);
}
}
- 例外のコンテキストテスト
class ExceptionContextTest extends TestCase
{
/** @test */
public function custom_exception_includes_correct_context()
{
$user = factory(User::class)->create();
$this->actingAs($user);
$exception = new CustomException('テストエラー');
$context = $exception->context();
$this->assertEquals($user->id, $context['user_id']);
$this->assertArrayHasKey('request_id', $context);
$this->assertArrayHasKey('url', $context);
}
/** @test */
public function debug_data_is_only_included_in_local_environment()
{
$exception = new CustomException('テストエラー');
// ローカル環境
app()['env'] = 'local';
$debugData = $exception->getDebugData();
$this->assertNotNull($debugData);
$this->assertArrayHasKey('trace', $debugData);
// 本番環境
app()['env'] = 'production';
$debugData = $exception->getDebugData();
$this->assertNull($debugData);
}
}
モックを使用した例外テスト
外部サービスとの連携における例外テストの方法です。
- サービスのモック化
class PaymentControllerTest extends TestCase
{
/** @test */
public function payment_failure_is_handled_correctly()
{
// PaymentServiceのモック作成
$paymentService = $this->mock(PaymentService::class);
// モックの振る舞いを定義
$paymentService->shouldReceive('processPayment')
->once()
->andThrow(new PaymentFailedException('決済エラー'));
// テストリクエストの実行
$response = $this->postJson('/api/payment', [
'amount' => 1000,
'card_number' => '1234-5678-9012-3456'
]);
// レスポンスの検証
$response->assertStatus(400)
->assertJson(['error' => '決済エラー']);
}
}
- 複雑な例外シナリオのテスト
class OrderProcessingTest extends TestCase
{
/** @test */
public function multiple_exceptions_are_handled_correctly()
{
// 依存サービスのモック
$this->mock(InventoryService::class, function ($mock) {
$mock->shouldReceive('checkStock')
->andThrow(new OutOfStockException('在庫不足'));
});
$this->mock(NotificationService::class, function ($mock) {
$mock->shouldReceive('notifyAdmin')
->andReturn(true);
});
// 例外処理の検証
try {
$orderService = app(OrderService::class);
$orderService->process($this->getTestOrder());
$this->fail('例外が発生しませんでした');
} catch (OutOfStockException $e) {
$this->assertEquals('在庫不足', $e->getMessage());
$this->assertNotNull($e->getOrder());
}
}
}
テスト環境での例外シミュレーション
様々な例外状況をシミュレートするテスト手法です。
- 例外発生条件のシミュレーション
class ExceptionSimulationTest extends TestCase
{
/** @test */
public function database_exceptions_are_handled()
{
// データベース例外のシミュレーション
DB::shouldReceive('transaction')
->once()
->andThrow(new QueryException(
'select * from users',
[],
new \Exception('データベースエラー')
));
$response = $this->post('/api/users', [
'name' => 'テストユーザー'
]);
$response->assertStatus(500)
->assertJson(['error' => 'システムエラーが発生しました']);
}
/** @test */
public function rate_limit_exceptions_are_handled()
{
// レート制限のシミュレーション
$this->withoutExceptionHandling();
for ($i = 0; $i < 61; $i++) {
$response = $this->getJson('/api/status');
}
$response->assertStatus(429)
->assertJson(['error' => 'Too Many Requests']);
}
}
- 非同期処理の例外テスト
class AsyncExceptionTest extends TestCase
{
/** @test */
public function failed_job_is_handled_correctly()
{
// ジョブ失敗のシミュレーション
Queue::fake();
ProcessPayment::dispatch($this->getTestOrder());
Queue::assertPushed(ProcessPayment::class, function ($job) {
return $job->order->id === $this->getTestOrder()->id;
});
// 失敗ハンドラーのテスト
Event::fake();
$job = new ProcessPayment($this->getTestOrder());
$job->failed(new \Exception('ジョブ失敗'));
Event::assertDispatched(JobFailed::class);
}
}
例外テストのポイント:
- 基本的な例外発生のテストを確実に行う
- モックを使用して外部依存を制御する
- 環境による動作の違いを考慮したテスト
- 複雑な例外シナリオもカバー
- 非同期処理の例外も適切にテスト
- セキュリティ関連の例外は特に注意深くテスト
これらのテスト手法を適切に実装することで、例外処理の信頼性を確保できます。