register_shutdown_function() 註冊的回調函數會在 PHP 腳本**正常結束**、**致命錯誤終止**、**超時中止**或**用戶強制停止**時執行。它是捕獲 E_ERROR、執行清理工作、記錄關鍵日誌的關鍵機制。本文詳細解析用法、應用場景與避坑要點。
<?php
// 註冊關閉回調
register_shutdown_function('shutdownHandler');
// 或
register_shutdown_function([$obj, 'method']);
// 或
register_shutdown_function(function() { /* ... */ });
// 腳本結束時自動呼叫 shutdownHandler()
?>
觸發場景:
die()/exit() 主動結束E_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR)max_execution_time)SIGTERM, SIGKILL 可能不觸發)<?php
// shutdown.php
header('Content-Type: text/html; charset=utf-8');
class ShutdownHandler {
public function handle() {
$error = error_get_last(); // 只能獲取最後一個錯誤
if ($error !== null) {
// 判斷是否為致命錯誤
$fatalTypes = [E_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR, E_CORE_ERROR, E_COMPILE_ERROR];
if (in_array($error['type'], $fatalTypes)) {
echo '';
echo "捕獲致命錯誤:\n";
print_r($error);
echo '';
// 記錄到檔案 (需用絕對路徑,因工作目錄可能已變更)
$logFile = 'D:/practise/php/Error/error.txt';
$msg = date('Y-m-d H:i:s') . " FATAL: {$error['message']} in {$error['file']}:{$error['line']}\n";
file_put_contents($logFile, $msg, FILE_APPEND | LOCK_EX);
}
}
die('腳本結束 - Shutdown handler 執行完畢');
}
}
// 註冊:注意需實例化後傳入陣列
register_shutdown_function([new ShutdownHandler(), 'handle']);
// 故意觸發致命錯誤
echo md6(); // 未定義函數 -> E_ERROR
?>
Fatal error: Call to undefined function md6() in D:\practise\php\Error\shutdown.php on line 28
Array
(
[type] => 1
[message] => Call to undefined function md6()
[file] => D:\practise\php\Error\shutdown.php
[line] => 28
)
腳本結束 - Shutdown handler 執行完畢
D:/practise/php/Error/error.txt 內容:
2024-01-15 10:30:45 FATAL: Call to undefined function md6() in D:\practise\php\Error\shutdown.php:28
<?php
$lockFile = fopen('/tmp/app.lock', 'w');
flock($lockFile, LOCK_EX);
register_shutdown_function(function() use ($lockFile) {
flock($lockFile, LOCK_UN);
fclose($lockFile);
@unlink('/tmp/app.lock');
});
// 業務邏輯...
?>
<?php
ob_start();
register_shutdown_function(function() {
$content = ob_get_clean();
// 統一後處理:壓縮、註入監控碼、寫入快取
echo $content;
});
?>
<?php
class LegacyClass {
function LegacyClass() {
register_shutdown_function([$this, '_destructor']);
}
function _destructor() {
// 清理邏輯
}
}
?>
<?php
$startTime = microtime(true);
$startMem = memory_get_usage();
register_shutdown_function(function() use ($startTime, $startMem) {
$time = microtime(true) - $startTime;
$mem = memory_get_peak_usage() - $startMem;
if ($time > 1.0 || $mem > 50 * 1024 * 1024) {
error_log("SLOW REQUEST: {$_SERVER['REQUEST_URI']} Time: {$time}s Mem: " . ($mem/1024/1024) . "MB");
}
});
?>
| 陷阱 | 說明 | 解決 |
|---|---|---|
| 工作目錄變更 | 關閉時 CWD 可能變為系統根目錄 | 檔案操作**必須使用絕對路徑** |
| 無法輸出 HTTP 標頭 | 關閉階段已過標頭發送期限 | 僅能輸出 Body 內容或寫入檔案/日誌 |
| 物件已銷毀 | 關閉時物件可能已 __destruct | 回調內不要依賴其他物件狀態 |
| 多次註冊順序 | 按註冊順序**反向**執行 (LIFO) | 注意依賴順序,或用單一回調統籌 |
error_get_last() 限制 | 只能取最後一個錯誤,且不含 E_PARSE | 配合 set_error_handler 記錄所有錯誤 |
| CLI vs Web 差異 | CLI 無超時限制,Web 受 max_execution_time | 測試需在實際運行環境驗證 |
| 特性 | register_shutdown_function | register_tick_function |
|---|---|---|
| 觸發時機 | 腳本結束一次 | 每個 tick (declare(ticks=N) 控制) |
| 用途 | 清理、致命錯誤捕獲、統計 | 效能分析、信號模擬、定期檢查 |
| 執行上下文 | 關閉階段 | 正常執行流中 |
symfony/error-handler、filp/whoops 提供漂亮錯誤頁、日誌整合App\Exceptions\Handler、Symfony Kernel::terminate()register_shutdown_function() 是 PHP 底層的「最後防線」,理解其執行語境與限制,能在關鍵時刻完成資源守護與錯誤見證。