PHP 腳本發生 Fatal Error(如類別不存在、require 失敗、記憶體耗盡)時,若 display_errors=Off 使用者看到空白頁;若 On 則暴露敏感堆疊資訊。使用 register_shutdown_function 可在腳本關閉前攔截致命錯誤,輸出友善提示或記錄日誌。
register_shutdown_function 註冊的回調函數會在腳本執行結束(正常結束或致命錯誤)時自動呼叫error_get_last() 可取得最後發生的錯誤資訊<?php
$flag = false; // 腳本正常結束旗標
function deal_error() {
global $flag;
if (!$flag) {
// 取得最後錯誤
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
// 記錄日誌(生產環境建議)
error_log("[FATAL] {$error['message']} in {$error['file']}:{$error['line']}");
// 輸出友善頁面(避免輸出敏感資訊)
http_response_code(500);
echo '
系統暫時無法服務
系統暫時無法服務
我們已記錄此錯誤,技術團隊正在處理中。
';
}
}
return false;
}
register_shutdown_function('deal_error');
// 業務邏輯區域
// $obj = new NotExistClass(); // 觸發 Fatal Error
require './test.php'; // 檔案不存在觸發 Fatal Error
$flag = true; // 正常結束標記
?>
class ErrorHandler {
private static $completed = false;
public static function register() {
register_shutdown_function([__CLASS__, 'handleShutdown']);
set_error_handler([__CLASS__, 'handleError']);
set_exception_handler([__CLASS__, 'handleException']);
}
public static function markCompleted() {
self::$completed = true;
}
public static function handleShutdown() {
if (self::$completed) return;
$error = error_get_last();
if (!$error) return;
$fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
if (!in_array($error['type'], $fatalTypes)) return;
self::respond($error, 500);
}
public static function handleError($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) return false;
$error = [
'type' => $severity,
'message' => $message,
'file' => $file,
'line' => $line
];
// 非致命錯誤記錄日誌但不中斷
error_log("[ERROR] {$message} in {$file}:{$line}");
return true; // 不執行內建錯誤處理
}
public static function handleException(Throwable $e) {
if (self::$completed) return;
$error = [
'type' => 'Exception',
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
self::respond($error, 500);
}
private static function respond(array $error, int $code) {
http_response_code($code);
// AJAX 請求回傳 JSON
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'error' => true,
'message' => '伺服器內部錯誤',
'code' => $code
], JSON_UNESCAPED_UNICODE);
return;
}
// 一般請求輸出 HTML
// ... 輸出友善錯誤頁面
}
}
// 入口文件最早引入
ErrorHandler::register();
// 業務代碼結束前
ErrorHandler::markCompleted();
| 可捕獲 | 不可捕獲 |
|---|---|
類別不存在 new NotExistClass() |
語法錯誤 (編譯階段失敗) |
| require/include 不存在檔案 | 超時中斷 max_execution_time(視 SAPI 而定) |
記憶體耗盡 memory_limit |
Process 被 kill (OOM Killer、SIGKILL) |
| 未捕獲的 Exception | PHP 內核崩潰 |
| 觸發 E_USER_ERROR | 語法錯誤如少分號、多括號 |