はじめに
PHPで例外処理をカスタマイズする際、一時的に例外ハンドラーを変更してから元に戻したいことがよくあります。そんな時に使うのがrestore_exception_handler関数です。
restore_exception_handler関数は、set_exception_handler()で設定したカスタム例外ハンドラーを解除して、以前のハンドラーに戻す機能を提供します。この記事では、restore_exception_handler関数の基本から実践的な使い方まで、詳しく解説していきます。
restore_exception_handler関数とは?
restore_exception_handler関数は、以前の例外ハンドラーを復元する関数です。set_exception_handler()でカスタムハンドラーを設定した後、元の状態に戻すために使用します。
基本的な構文
<?php
restore_exception_handler(): bool
?>
- 引数: なし
- 戻り値: 常にtrue
最もシンプルな使用例
<?php
// カスタム例外ハンドラーを設定
set_exception_handler(function($exception) {
echo "カスタムハンドラー: " . $exception->getMessage() . "\n";
});
throw new Exception("例外1"); // カスタムハンドラーが処理
// 例外ハンドラーを元に戻す
restore_exception_handler();
throw new Exception("例外2"); // デフォルトハンドラーが処理(Fatal error)
?>
例外ハンドラースタックの仕組み
PHPは例外ハンドラーもスタック構造で管理しています:
<?php
// ハンドラー1を設定
set_exception_handler(function($exception) {
echo "ハンドラー1: " . $exception->getMessage() . "\n";
});
// ハンドラー2を設定(ハンドラー1の上に積まれる)
set_exception_handler(function($exception) {
echo "ハンドラー2: " . $exception->getMessage() . "\n";
});
throw new Exception("テスト"); // ハンドラー2が処理
// 最新のハンドラー(ハンドラー2)を削除
restore_exception_handler();
throw new Exception("テスト2"); // ハンドラー1が処理
?>
実践的な使用例
1. 特定の処理だけカスタムハンドラーを使用
<?php
/**
* 一時的に例外ハンドラーを変更して処理を実行
*/
function executeWithCustomHandler($callback, $handler) {
// カスタムハンドラーを設定
set_exception_handler($handler);
try {
$result = $callback();
return $result;
} catch (Throwable $e) {
// ハンドラー内でキャッチされなかった場合
throw $e;
} finally {
// 必ず元に戻す
restore_exception_handler();
}
}
// グローバルハンドラー
set_exception_handler(function($exception) {
echo "[グローバル] " . $exception->getMessage() . "\n";
});
echo "=== 通常の例外 ===\n";
throw new Exception("グローバルハンドラーで処理");
echo "\n=== カスタムハンドラー内 ===\n";
executeWithCustomHandler(
function() {
throw new Exception("カスタムハンドラーで処理");
},
function($exception) {
echo "[カスタム] " . $exception->getMessage() . "\n";
echo " 詳細情報を表示\n";
echo " ファイル: " . $exception->getFile() . "\n";
echo " 行: " . $exception->getLine() . "\n";
}
);
echo "\n=== 復元後の例外 ===\n";
throw new Exception("再びグローバルハンドラーで処理");
?>
2. 例外の詳細ログ記録
<?php
/**
* 例外を詳細にログ記録するクラス
*/
class DetailedExceptionLogger {
private $logFile;
private $exceptions = [];
public function __construct($logFile = null) {
$this->logFile = $logFile ?? sys_get_temp_dir() . '/exceptions.log';
}
/**
* 詳細ログを有効にして処理を実行
*/
public function execute($callback) {
$this->exceptions = [];
// カスタム例外ハンドラーを設定
set_exception_handler(function($exception) {
$this->logException($exception);
});
try {
$result = $callback();
return $result;
} finally {
// ハンドラーを復元
restore_exception_handler();
}
}
/**
* 例外を詳細にログ記録
*/
private function logException($exception) {
$details = [
'time' => date('Y-m-d H:i:s'),
'class' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString()
];
$this->exceptions[] = $details;
// ログファイルに記録
$logEntry = sprintf(
"[%s] %s: %s\nFile: %s:%d\nTrace:\n%s\n%s\n",
$details['time'],
$details['class'],
$details['message'],
$details['file'],
$details['line'],
$details['trace'],
str_repeat('-', 80)
);
file_put_contents($this->logFile, $logEntry, FILE_APPEND);
// 画面にも表示
echo "╔══════════════════════════════════════╗\n";
echo "║ 例外が発生しました ║\n";
echo "╠══════════════════════════════════════╣\n";
printf("║ クラス: %-28s║\n", $details['class']);
printf("║ メッセージ: %-24s║\n", substr($details['message'], 0, 24));
printf("║ ファイル: %-26s║\n", basename($details['file']));
printf("║ 行番号: %-28d║\n", $details['line']);
echo "╚══════════════════════════════════════╝\n";
}
/**
* 記録された例外を取得
*/
public function getExceptions() {
return $this->exceptions;
}
/**
* ログファイルパスを取得
*/
public function getLogFile() {
return $this->logFile;
}
}
// 使用例
$logger = new DetailedExceptionLogger('./exceptions.log');
try {
$logger->execute(function() {
// 何か処理
throw new RuntimeException("データベース接続エラー");
});
} catch (Throwable $e) {
// ハンドラーで処理済み
}
echo "\nログファイル: " . $logger->getLogFile() . "\n";
?>
3. 環境別の例外処理
<?php
/**
* 環境に応じた例外ハンドラーの切り替え
*/
class EnvironmentExceptionHandler {
private $environment;
const ENV_PRODUCTION = 'production';
const ENV_DEVELOPMENT = 'development';
const ENV_TESTING = 'testing';
public function __construct($environment = self::ENV_PRODUCTION) {
$this->environment = $environment;
}
/**
* 環境に応じたハンドラーを有効化
*/
public function enable() {
switch ($this->environment) {
case self::ENV_DEVELOPMENT:
$handler = [$this, 'handleDevelopment'];
break;
case self::ENV_TESTING:
$handler = [$this, 'handleTesting'];
break;
case self::ENV_PRODUCTION:
default:
$handler = [$this, 'handleProduction'];
break;
}
set_exception_handler($handler);
}
/**
* ハンドラーを無効化
*/
public function disable() {
restore_exception_handler();
}
/**
* 本番環境用ハンドラー(詳細を隠す)
*/
private function handleProduction($exception) {
// ログに詳細を記録
error_log(sprintf(
"[EXCEPTION] %s: %s in %s:%d",
get_class($exception),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine()
));
// ユーザーには簡潔なメッセージのみ
http_response_code(500);
echo "申し訳ございません。システムエラーが発生しました。\n";
echo "エラーID: " . uniqid() . "\n";
}
/**
* 開発環境用ハンドラー(詳細を表示)
*/
private function handleDevelopment($exception) {
echo "╔══════════════════════════════════════════════════╗\n";
echo "║ 開発環境エラー情報 ║\n";
echo "╠══════════════════════════════════════════════════╣\n";
echo "║ クラス: " . get_class($exception) . "\n";
echo "║ メッセージ: " . $exception->getMessage() . "\n";
echo "║ ファイル: " . $exception->getFile() . ":" . $exception->getLine() . "\n";
echo "╠══════════════════════════════════════════════════╣\n";
echo "║ スタックトレース:\n";
echo $exception->getTraceAsString() . "\n";
echo "╚══════════════════════════════════════════════════╝\n";
}
/**
* テスト環境用ハンドラー(例外を記録)
*/
private function handleTesting($exception) {
// テスト用ログに記録
$logEntry = json_encode([
'class' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine()
]);
file_put_contents('/tmp/test_exceptions.log', $logEntry . "\n", FILE_APPEND);
echo "[TEST] Exception logged: " . $exception->getMessage() . "\n";
}
}
// 使用例
echo "=== 本番環境モード ===\n";
$prodHandler = new EnvironmentExceptionHandler(EnvironmentExceptionHandler::ENV_PRODUCTION);
$prodHandler->enable();
throw new Exception("本番環境エラー");
$prodHandler->disable();
echo "\n=== 開発環境モード ===\n";
$devHandler = new EnvironmentExceptionHandler(EnvironmentExceptionHandler::ENV_DEVELOPMENT);
$devHandler->enable();
throw new Exception("開発環境エラー");
?>
4. 例外の変換と再スロー
<?php
/**
* 例外を別の型に変換
*/
class ExceptionTransformer {
private $transformMap = [];
/**
* 変換ルールを追加
*/
public function addTransform($fromClass, $toClass, $messagePrefix = '') {
$this->transformMap[$fromClass] = [
'class' => $toClass,
'prefix' => $messagePrefix
];
}
/**
* 変換ハンドラーを有効にして処理を実行
*/
public function execute($callback) {
set_exception_handler(function($exception) {
$this->handleException($exception);
});
try {
$result = $callback();
restore_exception_handler();
return $result;
} catch (Throwable $e) {
restore_exception_handler();
throw $e;
}
}
/**
* 例外を処理(変換または再スロー)
*/
private function handleException($exception) {
$exceptionClass = get_class($exception);
if (isset($this->transformMap[$exceptionClass])) {
$transform = $this->transformMap[$exceptionClass];
$newClass = $transform['class'];
$prefix = $transform['prefix'];
$message = $prefix . $exception->getMessage();
$newException = new $newClass($message, $exception->getCode(), $exception);
echo "例外を変換: {$exceptionClass} → {$newClass}\n";
echo "メッセージ: {$message}\n";
} else {
echo "変換なし: {$exceptionClass}\n";
echo "メッセージ: " . $exception->getMessage() . "\n";
}
}
}
// カスタム例外クラス
class DatabaseException extends Exception {}
class ValidationException extends Exception {}
class ApplicationException extends Exception {}
// 使用例
$transformer = new ExceptionTransformer();
$transformer->addTransform(
RuntimeException::class,
ApplicationException::class,
'[APP] '
);
$transformer->addTransform(
InvalidArgumentException::class,
ValidationException::class,
'[VALIDATION] '
);
$transformer->execute(function() {
throw new RuntimeException("データベースエラー");
});
echo "\n";
$transformer->execute(function() {
throw new InvalidArgumentException("無効な引数");
});
?>
5. 例外のキャプチャとテスト
<?php
/**
* テスト用に例外をキャプチャ
*/
class ExceptionCapture {
private $captured = [];
private $active = false;
/**
* キャプチャを開始
*/
public function start() {
$this->captured = [];
$this->active = true;
set_exception_handler(function($exception) {
$this->captured[] = [
'class' => get_class($exception),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'time' => microtime(true)
];
// 例外の伝播を止めずに記録のみ
echo "[CAPTURED] " . get_class($exception) . ": " . $exception->getMessage() . "\n";
});
}
/**
* キャプチャを停止
*/
public function stop() {
if ($this->active) {
restore_exception_handler();
$this->active = false;
}
}
/**
* キャプチャされた例外を取得
*/
public function getExceptions() {
return $this->captured;
}
/**
* 例外がキャプチャされたかチェック
*/
public function hasExceptions() {
return !empty($this->captured);
}
/**
* 特定のクラスの例外があるかチェック
*/
public function hasExceptionOfType($className) {
foreach ($this->captured as $exception) {
if ($exception['class'] === $className) {
return true;
}
}
return false;
}
/**
* 特定のメッセージを含む例外があるかチェック
*/
public function hasExceptionWithMessage($message) {
foreach ($this->captured as $exception) {
if (strpos($exception['message'], $message) !== false) {
return true;
}
}
return false;
}
/**
* 例外数を取得
*/
public function count() {
return count($this->captured);
}
}
// テスト例
function testExceptionHandling() {
$capture = new ExceptionCapture();
echo "=== テスト開始 ===\n";
$capture->start();
// テスト1: RuntimeException
try {
throw new RuntimeException("テストエラー1");
} catch (Throwable $e) {
// キャプチャされる
}
// テスト2: InvalidArgumentException
try {
throw new InvalidArgumentException("テストエラー2");
} catch (Throwable $e) {
// キャプチャされる
}
$capture->stop();
// アサーション
echo "\n=== テスト結果 ===\n";
echo "例外数: " . $capture->count() . "\n";
echo "RuntimeExceptionあり: " .
($capture->hasExceptionOfType(RuntimeException::class) ? "✓" : "✗") . "\n";
echo "'テストエラー1'を含む: " .
($capture->hasExceptionWithMessage("テストエラー1") ? "✓" : "✗") . "\n";
// 詳細表示
echo "\n=== キャプチャされた例外 ===\n";
foreach ($capture->getExceptions() as $i => $ex) {
echo ($i + 1) . ". {$ex['class']}: {$ex['message']}\n";
}
}
testExceptionHandling();
?>
6. チェーン化された例外ハンドラー
<?php
/**
* 複数のハンドラーをチェーン実行
*/
class ExceptionHandlerChain {
private $handlers = [];
/**
* ハンドラーを追加
*/
public function addHandler(callable $handler) {
$this->handlers[] = $handler;
return $this;
}
/**
* チェーンを有効化
*/
public function enable() {
set_exception_handler(function($exception) {
foreach ($this->handlers as $handler) {
try {
$handler($exception);
} catch (Throwable $e) {
// ハンドラー内のエラーは無視
error_log("Handler error: " . $e->getMessage());
}
}
});
}
/**
* チェーンを無効化
*/
public function disable() {
restore_exception_handler();
}
}
// 使用例
$chain = new ExceptionHandlerChain();
// ハンドラー1: ログ記録
$chain->addHandler(function($exception) {
echo "[LOG] " . get_class($exception) . ": " . $exception->getMessage() . "\n";
file_put_contents('/tmp/exceptions.log',
date('Y-m-d H:i:s') . " - " . $exception->getMessage() . "\n",
FILE_APPEND);
});
// ハンドラー2: メール通知(例)
$chain->addHandler(function($exception) {
echo "[MAIL] 管理者にメール送信(例)\n";
// mail('admin@example.com', 'Exception', $exception->getMessage());
});
// ハンドラー3: 統計記録
$chain->addHandler(function($exception) {
echo "[STATS] 統計情報を更新\n";
});
$chain->enable();
throw new Exception("チェーンテスト例外");
?>
7. グレースフルシャットダウン
<?php
/**
* 例外発生時にクリーンアップを実行
*/
class GracefulShutdownHandler {
private $cleanupCallbacks = [];
/**
* クリーンアップコールバックを追加
*/
public function addCleanup(callable $callback, $description = '') {
$this->cleanupCallbacks[] = [
'callback' => $callback,
'description' => $description
];
}
/**
* ハンドラーを有効化
*/
public function enable() {
set_exception_handler(function($exception) {
echo "╔══════════════════════════════════════╗\n";
echo "║ 致命的エラーが発生しました ║\n";
echo "╠══════════════════════════════════════╣\n";
echo "║ " . $exception->getMessage() . "\n";
echo "╠══════════════════════════════════════╣\n";
echo "║ クリーンアップを実行中... ║\n";
echo "╚══════════════════════════════════════╝\n\n";
$this->performCleanup();
echo "\nクリーンアップ完了。プログラムを終了します。\n";
});
}
/**
* ハンドラーを無効化
*/
public function disable() {
restore_exception_handler();
}
/**
* クリーンアップを実行
*/
private function performCleanup() {
foreach ($this->cleanupCallbacks as $cleanup) {
$desc = $cleanup['description'] ?: 'クリーンアップ処理';
echo "実行中: {$desc}...";
try {
$cleanup['callback']();
echo " ✓\n";
} catch (Throwable $e) {
echo " ✗ (エラー: {$e->getMessage()})\n";
}
}
}
}
// 使用例
$shutdown = new GracefulShutdownHandler();
// クリーンアップタスクを登録
$shutdown->addCleanup(function() {
// データベース接続を閉じる
echo "データベース接続をクローズ";
}, 'データベース接続のクローズ');
$shutdown->addCleanup(function() {
// 一時ファイルを削除
echo "一時ファイルを削除";
}, '一時ファイルの削除');
$shutdown->addCleanup(function() {
// ロックを解放
echo "ロックファイルを解放";
}, 'ロックの解放');
$shutdown->enable();
// 処理中に例外が発生
throw new Exception("予期しないエラーが発生しました");
?>
よくある問題と解決策
問題1: restore忘れによるハンドラーのリーク
<?php
// ❌ 悪い例
function badExample() {
set_exception_handler(function($e) {
echo "カスタムハンドラー\n";
});
// restore_exception_handler()を呼び忘れ!
}
// ✅ 良い例: finallyで確実に復元
function goodExample() {
set_exception_handler(function($e) {
echo "カスタムハンドラー\n";
});
try {
// 処理
} finally {
restore_exception_handler();
}
}
?>
問題2: 例外ハンドラー内での例外
<?php
// ハンドラー内でエラーが発生すると致命的
set_exception_handler(function($exception) {
// ✅ try-catchで保護
try {
// ログ記録などの処理
throw new Exception("ハンドラー内エラー");
} catch (Throwable $e) {
error_log("Handler error: " . $e->getMessage());
}
});
?>
まとめ
restore_exception_handler関数のポイントをおさらいしましょう:
- set_exception_handler()で設定したハンドラーを削除
- スタック構造で管理されている
- finallyブロックで確実に復元するのがベストプラクティス
- 環境別の例外処理に便利
- 複数回呼び出しても問題ない
- テスト用の例外キャプチャに最適
- 必ず対応するset_exception_handler()とペアで使用
restore_exception_handler関数を適切に使用することで、柔軟で保守性の高い例外処理を実現できます。特にfinallyブロックと組み合わせることで、確実にハンドラーを復元できます!
参考リンク
この記事が役に立ったら、ぜひシェアしてください!PHPに関する他の記事もお楽しみに。
