PHP カスタムエラーハンドラー
PHP デフォルトのエラー処理メカニズムは、堅牢なアプリケーションを構築する際には不十分な場合が多いです。カスタムエラーハンドラー(Custom error handlers) は、PHPのエラーや例外(Exceptions)をインターセプトする方法を提供し、詳細なログ情報の記録、ユーザーフレンドリーなエラーページの表示、さらには特定のエラー状態からの復旧試行など、レスポンス戦略をカスタマイズすることを可能にします。これにより、開発者はエラー管理を一元化し、アプリケーションの耐障害性を高め、一貫したユーザーエクスペリエンスを提供できるようになります。
1. カスタムエラーハンドラーの登録
PHPは、ユーザー定義の関数を登録するための set_error_handler() 関数を提供しています。スクリプトの実行中に発生するすべてのエラー(一部の例外を除く)は、この関数に渡されて処理されます。
注意: 以下のレベルの深刻なエラーはハンドリングできません:E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING、および set_error_handler() が呼び出されたファイル内で発生するほとんどの E_STRICT エラー。これらの致命的(Fatal)なエラーは通常、スクリプトを即座に終了させるため、優雅に処理することはできません。
カスタムエラーハンドラー関数は、少なくとも以下の2つの引数を受け取る必要があります:
- int $errno: 発生したエラーレベル(数値)。
- string $errstr: エラーの説明メッセージ。
また、より詳細なコンテキスト(Context)を取得するために、オプションで以下の3つの引数を受け取ることができます:
- string $errfile: エラーが発生したファイル名。
- int $errline: エラーが発生した行番号。
- array $errcontext: エラー発生時のアクティブなシンボルテーブルを指す配列(当時の変数状態が含まれます)。
戻り値のメカニズム:
- 関数が
trueを返すと、PHP標準のエラーハンドラーはスキップされます。 falseを返す(または何も返さない/nullを返す)場合、カスタム関数の実行後、PHP標準のエラーハンドラーも引き続き実行されます。
<?php
// 1. カスタムエラーハンドラー関数の定義
function myCustomErrorHandler(int $errno, string $errstr, ?string $errfile = null, ?int $errline = null, ?array $errcontext = null) {
// エラー番号を人間が読めるタイプに変換
$errorType = "未知のエラー";
switch ($errno) {
case E_USER_ERROR: $errorType = "ユーザーレベルの致命的エラー"; break;
case E_USER_WARNING: $errorType = "ユーザーレベルの警告"; break;
case E_USER_NOTICE: $errorType = "ユーザーレベルの通知"; break;
case E_WARNING: $errorType = "システム警告"; break;
case E_NOTICE: $errorType = "システム通知"; break;
case E_DEPRECATED: $errorType = "非推奨機能の警告"; break;
default: $errorType = "システムエラー (レベル: " . $errno . ")"; break;
}
// 2. ロギング用の詳細情報を準備
$logMessage = "[" . date("Y-m-d H:i:s") . "] " . $errorType . ": " . $errstr;
if ($errfile) $logMessage .= " ファイル: " . $errfile;
if ($errline) $logMessage .= " 行番号: " . $errline;
$logMessage .= PHP_EOL;
// (通常はここで $logMessage をログファイルに書き込みます。例:error_log を使用)
// 3. エラーレベルに基づいてページへの表示方法を決定
if ($errno === E_USER_ERROR) {
echo "<div style='color: red; border: 1px solid red; padding: 10px;'>";
echo "<h1>予期せぬエラーが発生しました。</h1>";
echo "<p>ご不便をおかけして申し訳ありません。エンジニアチームに通知されました。</p>";
echo "</div>";
// 致命的エラーの場合は通常スクリプトを中断する
exit(1);
} else {
// 警告や通知については、display_errors が有効な場合のみ表示する
if (ini_get('display_errors')) {
echo "<p style='color: orange;'><strong>" . $errorType . ":</strong> " . $errstr . "</p>";
}
}
// 4. true を返して PHP デフォルトのエラー出力を阻止し、完全に制御する
return true;
}
// カスタム関数の登録
set_error_handler("myCustomErrorHandler");
// テスト実行:
trigger_error("これはカスタムで生成されたユーザー警告です。", E_USER_WARNING);
echo "ゼロ除算を試行中...<br>";
$result = 10 / 0; // これはネイティブの E_WARNING を発生させ、関数でキャッチされます
echo "エラー処理が完了しました。スクリプトの実行を継続します。<br>";
// PHPデフォルトのエラーハンドラーに戻したい場合:
// restore_error_handler();
?>この例では、trigger_error() による手動のエラーも、ゼロ除算のようなネイティブコードによる E_WARNING も、すべて myCustomErrorHandler が全権を持って処理します。
2. キャッチ可能な致命的エラー (E_RECOVERABLE_ERROR) の処理
set_error_handler() では E_ERROR をキャッチできませんが、E_RECOVERABLE_ERROR はキャッチ可能です。このタイプのエラーは放置すると通常スクリプトが終了しますが、PHPは「救済」の機会を与えてくれます。典型的なシナリオは、厳密な型検査(declare(strict_types=1))が有効な際に、関数へ誤った型の引数を渡した場合です。
<?php
declare(strict_types=1); // 厳密な型検査を有効化
function myRecoverableErrorHandler(int $errno, string $errstr, ?string $errfile = null, ?int $errline = null, ?array $errcontext = null) {
echo "<div style='background-color: #ffeeee; border: 1px solid red; padding: 10px;'>";
echo "<h3>キャッチ可能な致命的エラーをインターセプトしました!</h3>";
echo "<p><strong>メッセージ:</strong> " . htmlspecialchars($errstr) . "</p>";
echo "<p>スクリプトを優雅に継続させるよう試みています...</p>";
echo "</div>";
// ロギングなど...
return true; // PHPデフォルトの致命的エラー挙動を阻止
}
set_error_handler("myRecoverableErrorHandler");
function greet(string $name): string {
return "こんにちは、" . $name . "さん!";
}
echo greet("Alice") . "<br>";
// 次の行は E_RECOVERABLE_ERROR を発生させます。greet は string を期待していますが int が渡されたためです
echo greet(123) . "<br>";
echo "エラーをインターセプトしたため、スクリプトはクラッシュせずに実行を継続しています。<br>";
?>カスタムハンドラーがない場合、整数 123 を渡すと即座に Fatal Error となりページがクラッシュします。カスタムハンドラーを介することで、エラーを記録するだけでなく、アプリケーションのレジリエンス(弾力性)を高めることができます。
3. エンタープライズレベルのアプリケーション戦略
3.1 開発環境と本番環境の切り分け
エラーレポートの戦略は、開発環境と本番環境(プロダクション)で天と地ほどの差があるべきです。開発中は詳細なエラーを即座に確認する必要がありますが、本番環境では静かにログを記録し、ユーザーには友好的な汎用エラーページを表示して、機密性の高いパス情報などの漏洩を防ぐ必要があります。
<?php
define('APP_ENV', 'production'); // 現在の環境を本番環境と仮定
function masterErrorHandler(int $errno, string $errstr, ?string $errfile = null, ?int $errline = null) {
// 1. どのような環境でも、まずは詳細なログを記録
$logMsg = date("[Y-m-d H:i:s]") . " [$errno] $errstr in $errfile:$errline" . PHP_EOL;
error_log($logMsg, 3, "/var/log/my_app_errors.log");
// 2. 環境に応じて表示戦略を決定
if (APP_ENV === 'development') {
// 開発環境:詳細情報をそのまま画面に出力
echo "<pre style='background:#f4f4f4; border:1px solid #ccc; padding:10px;'>";
echo "<b>🔥 DEVELOPMENT ERROR:</b>\n$logMsg</pre>";
} else {
// 本番環境:深刻なエラーの場合、出力バッファをクリアして友好的な 500 ページを表示
if ($errno === E_USER_ERROR || $errno === E_RECOVERABLE_ERROR) {
ob_clean(); // 途中で出力された可能性のある不完全なページをクリア
include 'friendly_500_error_page.html';
exit(1);
}
}
return true;
}
set_error_handler("masterErrorHandler");
?>3.2 究極の統合:従来のエラーを例外 (Exceptions) に変換する
従来の PHP エラー(Errors)と、モダンなオブジェクト指向の例外(Exceptions)は独立した2つのシステムです。これらを一元管理するために、業界で最も推奨されるベストプラクティスは、カスタムエラーハンドラー内ですべての従来のエラーを ErrorException オブジェクトにラップしてスロー(Throw)することです。
これにより、プロジェクト全体で try...catch ブロックを使用して、あらゆるタイプの問題を統一的に処理できるようになります。
<?php
// エラーを例外に変換するハンドラーの定義
function exceptionOnErrorHandler(int $errno, string $errstr, ?string $errfile = null, ?int $errline = null) {
// error_reporting の設定を尊重し、無視されているレベルの場合は例外を投げない
if (!(error_reporting() & $errno)) {
return false;
}
// エラーを ErrorException としてパッケージ化してスロー
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
// コンバーターを登録
set_error_handler("exceptionOnErrorHandler");
// これで、例外処理と同じ方法で従来の PHP 警告(Warning)を扱えます!
try {
echo "ゼロ除算を試行中...<br>";
$result = 10 / 0; // このネイティブの E_WARNING は、スローされた例外として扱われます
} catch (ErrorException $e) {
echo "<div style='color: blue; padding: 10px; border: 1px solid blue;'>";
echo "<h3>🎉 ErrorException のキャッチに成功 (PHP警告からの変換)!</h3>";
echo "内容:" . $e->getMessage();
echo "</div>";
}
?>この戦略は、アプリケーション全体の制御フローを劇的に簡素化します。これは Laravel や Symfony といったモダンな PHP フレームワークの低層エラー処理メカニズムにおいても、核心となる設計原理です。