PHP 入門

PHP カスタムエラーハンドラー

PHP デフォルトのエラー処理メカニズムは、堅牢なアプリケーションを構築する際には不十分な場合が多いです。カスタムエラーハンドラー(Custom error handlers) は、PHPのエラーや例外(Exceptions)をインターセプトする方法を提供し、詳細なログ情報の記録、ユーザーフレンドリーなエラーページの表示、さらには特定のエラー状態からの復旧試行など、レスポンス戦略をカスタマイズすることを可能にします。これにより、開発者はエラー管理を一元化し、アプリケーションの耐障害性を高め、一貫したユーザーエクスペリエンスを提供できるようになります。

1. カスタムエラーハンドラーの登録

PHPは、ユーザー定義の関数を登録するための set_error_handler() 関数を提供しています。スクリプトの実行中に発生するすべてのエラー(一部の例外を除く)は、この関数に渡されて処理されます。

       注意: 以下のレベルの深刻なエラーはハンドリングできません:E_ERRORE_PARSEE_CORE_ERRORE_CORE_WARNINGE_COMPILE_ERRORE_COMPILE_WARNING、および set_error_handler() が呼び出されたファイル内で発生するほとんどの E_STRICT エラー。これらの致命的(Fatal)なエラーは通常、スクリプトを即座に終了させるため、優雅に処理することはできません。

カスタムエラーハンドラー関数は、少なくとも以下の2つの引数を受け取る必要があります:

  1. int $errno: 発生したエラーレベル(数値)。
  2. string $errstr: エラーの説明メッセージ。

また、より詳細なコンテキスト(Context)を取得するために、オプションで以下の3つの引数を受け取ることができます:

  1. string $errfile: エラーが発生したファイル名。
  2. int $errline: エラーが発生した行番号。
  3. 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>";
}
?>

この戦略は、アプリケーション全体の制御フローを劇的に簡素化します。これは LaravelSymfony といったモダンな PHP フレームワークの低層エラー処理メカニズムにおいても、核心となる設計原理です。