[PHP]restore_error_handler関数の使い方を徹底解説!エラーハンドラーの復元

PHP

はじめに

PHPでエラーハンドリングをカスタマイズする際、一時的にエラーハンドラーを変更してから元に戻したいことがよくあります。そんな時に使うのがrestore_error_handler関数です。

restore_error_handler関数は、set_error_handler()で設定したカスタムエラーハンドラーを解除して、以前のハンドラーに戻す機能を提供します。この記事では、restore_error_handler関数の基本から実践的な使い方まで、詳しく解説していきます。

restore_error_handler関数とは?

restore_error_handler関数は、以前のエラーハンドラーを復元する関数です。set_error_handler()でカスタムハンドラーを設定した後、元の状態に戻すために使用します。

基本的な構文

<?php
restore_error_handler(): bool
?>
  • 引数: なし
  • 戻り値: 常にtrue

最もシンプルな使用例

<?php
// カスタムエラーハンドラーを設定
set_error_handler(function($errno, $errstr) {
    echo "カスタムハンドラー: {$errstr}\n";
});

trigger_error("エラー1");  // カスタムハンドラーが処理

// エラーハンドラーを元に戻す
restore_error_handler();

trigger_error("エラー2");  // デフォルトハンドラーが処理
?>

エラーハンドラースタックの仕組み

PHPはエラーハンドラーをスタック構造で管理しています:

<?php
// 初期状態: デフォルトハンドラー

// ハンドラー1を設定
set_error_handler(function($errno, $errstr) {
    echo "ハンドラー1: {$errstr}\n";
});

// ハンドラー2を設定(ハンドラー1の上に積まれる)
set_error_handler(function($errno, $errstr) {
    echo "ハンドラー2: {$errstr}\n";
});

trigger_error("テスト");  // ハンドラー2が処理

// 最新のハンドラー(ハンドラー2)を削除
restore_error_handler();

trigger_error("テスト2");  // ハンドラー1が処理

// ハンドラー1を削除
restore_error_handler();

trigger_error("テスト3");  // デフォルトハンドラーが処理
?>

実践的な使用例

1. 特定の処理だけカスタムハンドラーを使用

<?php
function processWithCustomErrorHandler($callback) {
    // カスタムエラーハンドラーを設定
    set_error_handler(function($errno, $errstr, $errfile, $errline) {
        echo "カスタムエラー処理:\n";
        echo "  レベル: {$errno}\n";
        echo "  メッセージ: {$errstr}\n";
        echo "  ファイル: {$errfile}:{$errline}\n";
        return true; // エラーの伝播を止める
    });
    
    try {
        // 処理実行
        $result = $callback();
        return $result;
    } finally {
        // 必ず元に戻す
        restore_error_handler();
    }
}

// 使用例
echo "=== 通常のエラー ===\n";
trigger_error("通常のエラー");

echo "\n=== カスタムハンドラー内 ===\n";
processWithCustomErrorHandler(function() {
    trigger_error("カスタムハンドラー内のエラー");
    return "処理完了";
});

echo "\n=== 復元後のエラー ===\n";
trigger_error("復元後のエラー");
?>

2. エラーを一時的に抑制

<?php
/**
 * エラーを一時的に抑制して処理を実行
 */
function suppressErrors($callback) {
    // エラーを無視するハンドラーを設定
    set_error_handler(function() {
        // 何もしない(エラーを抑制)
        return true;
    });
    
    try {
        return $callback();
    } finally {
        restore_error_handler();
    }
}

// 使用例
echo "=== エラー抑制前 ===\n";
$array = [];
echo $array['nonexistent'];  // Notice: Undefined index

echo "\n=== エラー抑制中 ===\n";
$result = suppressErrors(function() use ($array) {
    return $array['nonexistent'] ?? 'デフォルト値';
});
echo "結果: {$result}\n";

echo "\n=== エラー抑制後 ===\n";
echo $array['nonexistent'];  // Notice: Undefined index
?>

3. エラーログ記録の一時的な変更

<?php
class TemporaryErrorLogger {
    private $logFile;
    private $errors = [];
    
    public function __construct($logFile = null) {
        $this->logFile = $logFile;
    }
    
    /**
     * カスタムログ記録を有効にして処理を実行
     */
    public function execute($callback) {
        $this->errors = [];
        
        // カスタムエラーハンドラーを設定
        set_error_handler(function($errno, $errstr, $errfile, $errline) {
            $error = [
                'time' => date('Y-m-d H:i:s'),
                'level' => $this->getErrorLevel($errno),
                'message' => $errstr,
                'file' => $errfile,
                'line' => $errline
            ];
            
            $this->errors[] = $error;
            
            // ログファイルに記録
            if ($this->logFile) {
                $logEntry = sprintf(
                    "[%s] %s: %s in %s:%d\n",
                    $error['time'],
                    $error['level'],
                    $error['message'],
                    $error['file'],
                    $error['line']
                );
                file_put_contents($this->logFile, $logEntry, FILE_APPEND);
            }
            
            // 画面にも表示
            echo "[{$error['level']}] {$error['message']}\n";
            
            return true;
        });
        
        try {
            $result = $callback();
            return $result;
        } finally {
            // エラーハンドラーを復元
            restore_error_handler();
        }
    }
    
    /**
     * 記録されたエラーを取得
     */
    public function getErrors() {
        return $this->errors;
    }
    
    /**
     * エラーレベルを文字列に変換
     */
    private function getErrorLevel($errno) {
        $levels = [
            E_ERROR => 'ERROR',
            E_WARNING => 'WARNING',
            E_NOTICE => 'NOTICE',
            E_USER_ERROR => 'USER_ERROR',
            E_USER_WARNING => 'USER_WARNING',
            E_USER_NOTICE => 'USER_NOTICE',
            E_STRICT => 'STRICT',
            E_DEPRECATED => 'DEPRECATED'
        ];
        
        return $levels[$errno] ?? 'UNKNOWN';
    }
}

// 使用例
$logger = new TemporaryErrorLogger('./temp_errors.log');

$result = $logger->execute(function() {
    // いくつかの警告を発生させる
    $array = [];
    $value1 = $array['key1'];  // Notice
    
    trigger_error("カスタム警告", E_USER_WARNING);
    
    $value2 = $array['key2'];  // Notice
    
    return "処理完了";
});

echo "\n=== 記録されたエラー一覧 ===\n";
foreach ($logger->getErrors() as $error) {
    echo "{$error['time']} - {$error['level']}: {$error['message']}\n";
}
?>

4. try-catchスタイルのエラーハンドリング

<?php
/**
 * エラーを例外として扱う
 */
class ErrorAsException {
    /**
     * エラーを例外に変換して処理
     */
    public static function handle($callback) {
        // エラーを例外に変換するハンドラー
        set_error_handler(function($errno, $errstr, $errfile, $errline) {
            throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
        });
        
        try {
            $result = $callback();
            restore_error_handler();
            return $result;
        } catch (ErrorException $e) {
            restore_error_handler();
            throw $e;
        }
    }
}

// 使用例
try {
    ErrorAsException::handle(function() {
        $array = [];
        echo $array['nonexistent'];  // 例外として扱われる
    });
} catch (ErrorException $e) {
    echo "例外をキャッチ: {$e->getMessage()}\n";
    echo "ファイル: {$e->getFile()}:{$e->getLine()}\n";
}

// ハンドラーが復元されているか確認
echo "\n復元後のエラー:\n";
$array = [];
echo @$array['test'] ?? 'デフォルト';  // 通常のエラーハンドリング
?>

5. ネストしたエラーハンドラーの管理

<?php
/**
 * エラーハンドラーのスタック管理
 */
class ErrorHandlerStack {
    private static $stack = [];
    
    /**
     * エラーハンドラーをプッシュ
     */
    public static function push($handler) {
        self::$stack[] = $handler;
        set_error_handler($handler);
    }
    
    /**
     * エラーハンドラーをポップ
     */
    public static function pop() {
        if (!empty(self::$stack)) {
            array_pop(self::$stack);
            restore_error_handler();
            return true;
        }
        return false;
    }
    
    /**
     * スタックをクリア
     */
    public static function clear() {
        while (!empty(self::$stack)) {
            self::pop();
        }
    }
    
    /**
     * 現在のスタック深度を取得
     */
    public static function depth() {
        return count(self::$stack);
    }
}

// 使用例
echo "初期深度: " . ErrorHandlerStack::depth() . "\n\n";

// レベル1のハンドラー
ErrorHandlerStack::push(function($errno, $errstr) {
    echo "[レベル1] {$errstr}\n";
    return true;
});

trigger_error("エラー1");
echo "深度: " . ErrorHandlerStack::depth() . "\n\n";

// レベル2のハンドラー
ErrorHandlerStack::push(function($errno, $errstr) {
    echo "[レベル2] {$errstr}\n";
    return true;
});

trigger_error("エラー2");
echo "深度: " . ErrorHandlerStack::depth() . "\n\n";

// レベル3のハンドラー
ErrorHandlerStack::push(function($errno, $errstr) {
    echo "[レベル3] {$errstr}\n";
    return true;
});

trigger_error("エラー3");
echo "深度: " . ErrorHandlerStack::depth() . "\n\n";

// 1つずつ戻す
ErrorHandlerStack::pop();
trigger_error("レベル2に戻った");

ErrorHandlerStack::pop();
trigger_error("レベル1に戻った");

// すべてクリア
ErrorHandlerStack::clear();
echo "\n全てクリア後の深度: " . ErrorHandlerStack::depth() . "\n";
?>

6. デバッグモードの切り替え

<?php
/**
 * デバッグモードでエラー表示を詳細化
 */
class DebugMode {
    private static $isDebug = false;
    
    /**
     * デバッグモードを有効化
     */
    public static function enable() {
        self::$isDebug = true;
        
        set_error_handler(function($errno, $errstr, $errfile, $errline, $errcontext) {
            $levelName = self::getErrorLevelName($errno);
            
            echo "\n╔══════════════════════════════════════╗\n";
            echo "║         デバッグ情報                 ║\n";
            echo "╠══════════════════════════════════════╣\n";
            printf("║ レベル: %-28s║\n", $levelName);
            printf("║ メッセージ: %-24s║\n", substr($errstr, 0, 24));
            printf("║ ファイル: %-26s║\n", basename($errfile));
            printf("║ 行番号: %-28d║\n", $errline);
            echo "╠══════════════════════════════════════╣\n";
            echo "║ スタックトレース:                    ║\n";
            
            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
            foreach (array_slice($trace, 1, 3) as $i => $frame) {
                $func = $frame['function'] ?? 'unknown';
                $file = basename($frame['file'] ?? 'unknown');
                $line = $frame['line'] ?? 0;
                printf("║ #%d %s (%s:%d)\n", $i, $func, $file, $line);
            }
            
            echo "╚══════════════════════════════════════╝\n";
            
            return true;
        });
    }
    
    /**
     * デバッグモードを無効化
     */
    public static function disable() {
        if (self::$isDebug) {
            self::$isDebug = false;
            restore_error_handler();
        }
    }
    
    /**
     * デバッグモードの状態を確認
     */
    public static function isEnabled() {
        return self::$isDebug;
    }
    
    private static function getErrorLevelName($errno) {
        $levels = [
            E_ERROR => 'E_ERROR',
            E_WARNING => 'E_WARNING',
            E_NOTICE => 'E_NOTICE',
            E_USER_ERROR => 'E_USER_ERROR',
            E_USER_WARNING => 'E_USER_WARNING',
            E_USER_NOTICE => 'E_USER_NOTICE'
        ];
        return $levels[$errno] ?? "UNKNOWN ({$errno})";
    }
}

// 使用例
echo "=== 通常モード ===\n";
trigger_error("通常のエラー", E_USER_NOTICE);

echo "\n=== デバッグモード有効化 ===\n";
DebugMode::enable();
trigger_error("デバッグモードのエラー", E_USER_WARNING);

echo "\n=== デバッグモード無効化 ===\n";
DebugMode::disable();
trigger_error("通常に戻った", E_USER_NOTICE);
?>

7. ユニットテスト用のエラーキャプチャ

<?php
/**
 * テスト用にエラーをキャプチャ
 */
class ErrorCapture {
    private $captured = [];
    private $active = false;
    
    /**
     * エラーキャプチャを開始
     */
    public function start() {
        $this->captured = [];
        $this->active = true;
        
        set_error_handler(function($errno, $errstr, $errfile, $errline) {
            $this->captured[] = [
                'errno' => $errno,
                'errstr' => $errstr,
                'errfile' => $errfile,
                'errline' => $errline,
                'time' => microtime(true)
            ];
            return true; // エラーの伝播を止める
        });
    }
    
    /**
     * エラーキャプチャを停止
     */
    public function stop() {
        if ($this->active) {
            restore_error_handler();
            $this->active = false;
        }
    }
    
    /**
     * キャプチャされたエラーを取得
     */
    public function getErrors() {
        return $this->captured;
    }
    
    /**
     * エラーがキャプチャされたかチェック
     */
    public function hasErrors() {
        return !empty($this->captured);
    }
    
    /**
     * 特定のエラーメッセージが含まれるかチェック
     */
    public function hasError($message) {
        foreach ($this->captured as $error) {
            if (strpos($error['errstr'], $message) !== false) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * エラー数を取得
     */
    public function count() {
        return count($this->captured);
    }
    
    /**
     * クリア
     */
    public function clear() {
        $this->captured = [];
    }
}

// テストケース例
function testFunction() {
    $capture = new ErrorCapture();
    
    echo "=== テスト開始 ===\n";
    $capture->start();
    
    // テスト対象の処理
    $array = [];
    $value1 = $array['key1'];  // Notice
    trigger_error("テスト警告", E_USER_WARNING);
    $value2 = $array['key2'];  // Notice
    
    $capture->stop();
    
    // アサーション
    echo "エラー数: " . $capture->count() . "\n";
    echo "警告が含まれる: " . ($capture->hasError("テスト警告") ? "はい" : "いいえ") . "\n";
    
    echo "\n=== キャプチャされたエラー ===\n";
    foreach ($capture->getErrors() as $i => $error) {
        echo ($i + 1) . ". {$error['errstr']}\n";
    }
}

testFunction();
?>

よくある問題と解決策

問題1: restore忘れによるハンドラーのリーク

<?php
// ❌ 悪い例: restoreを忘れる
function badExample() {
    set_error_handler(function($errno, $errstr) {
        echo "カスタムハンドラー\n";
        return true;
    });
    
    // 処理...
    
    // restore_error_handler()を呼び忘れ!
}

badExample();
trigger_error("テスト");  // カスタムハンドラーが残っている

// ✅ 良い例: finallyブロックで確実に復元
function goodExample() {
    set_error_handler(function($errno, $errstr) {
        echo "カスタムハンドラー\n";
        return true;
    });
    
    try {
        // 処理...
    } finally {
        restore_error_handler();
    }
}

goodExample();
trigger_error("テスト");  // デフォルトハンドラー
?>

問題2: 多重restore

<?php
// 複数回restoreしても問題ない(無視される)
set_error_handler(function() { return true; });

restore_error_handler();  // OK
restore_error_handler();  // OK(何も起こらない)
restore_error_handler();  // OK(何も起こらない)
?>

問題3: ハンドラー内での復元

<?php
// ❌ ハンドラー内でrestoreするとハンドラーが即座に削除される
set_error_handler(function($errno, $errstr) {
    echo "ハンドラー実行\n";
    restore_error_handler();  // 自分自身を削除
    return true;
});

trigger_error("エラー1");  // ハンドラー実行→削除
trigger_error("エラー2");  // デフォルトハンドラー
?>

まとめ

restore_error_handler関数のポイントをおさらいしましょう:

  1. set_error_handler()で設定したハンドラーを削除
  2. スタック構造で管理されている
  3. finallyブロックで確実に復元するのがベストプラクティス
  4. 一時的なエラーハンドリングの変更に最適
  5. 複数回呼び出しても問題ない
  6. デバッグモードの切り替えに便利
  7. 必ず対応するset_error_handler()とペアで使用

restore_error_handler関数を適切に使用することで、柔軟で保守性の高いエラーハンドリングを実現できます。特にfinallyブロックと組み合わせることで、確実にハンドラーを復元できます!

参考リンク


この記事が役に立ったら、ぜひシェアしてください!PHPに関する他の記事もお楽しみに。

タイトルとURLをコピーしました