はじめに
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関数のポイントをおさらいしましょう:
- set_error_handler()で設定したハンドラーを削除
- スタック構造で管理されている
- finallyブロックで確実に復元するのがベストプラクティス
- 一時的なエラーハンドリングの変更に最適
- 複数回呼び出しても問題ない
- デバッグモードの切り替えに便利
- 必ず対応するset_error_handler()とペアで使用
restore_error_handler関数を適切に使用することで、柔軟で保守性の高いエラーハンドリングを実現できます。特にfinallyブロックと組み合わせることで、確実にハンドラーを復元できます!
参考リンク
この記事が役に立ったら、ぜひシェアしてください!PHPに関する他の記事もお楽しみに。
