set_error_handler() 允許開發者接管 PHP 運行時錯誤 (E_WARNING、E_NOTICE 等),實現自定義日誌、安全輸出、異常轉換、條件調試。本文系統講解語法、三種回調註冊方式、錯誤類型過濾、生產/開發環境差異化配置、與 register_shutdown_function 協作捕獲致命錯誤。
set_error_handler(callable $callback, int $error_types = E_ALL | E_STRICT): callable|null
$callback:function(int $errno, string $errstr, string $errfile, int $errline, array $errcontext): bool$error_types:要攔截的錯誤級別位掩碼。無法攔截:E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING。<?php
// 1. 普通函數
function myErrorHandler($errno, $errstr, $errfile, $errline) { ... }
set_error_handler('myErrorHandler');
// 2. 類靜態方法
class ErrorHandler {
public static function handle($errno, $errstr, $errfile, $errline) { ... }
}
set_error_handler(['ErrorHandler', 'handle']);
// 3. 對象實例方法 (需引用傳遞 $this)
class ErrorHandler {
public function handle($errno, $errstr, $errfile, $errline) { ... }
}
$handler = new ErrorHandler();
set_error_handler([$handler, 'handle']); // PHP 5.4+ 不需 &
// 4. 匿名函數 (PHP 5.3+)
set_error_handler(function($errno, $errstr, $errfile, $errline) {
// 邏輯
});
?>
<?php
function productionErrorHandler(int $errno, string $errstr, string $errfile, int $errline): bool {
// 1. 判斷是否在錯誤報告級別內 (受 error_reporting() 影響)
if (!(error_reporting() & $errno)) {
return false; // 標準處理器會忽略,這裡也忽略
}
// 2. 映射錯誤級別到人類可讀名稱
$errNames = [
E_ERROR => 'ERROR',
E_WARNING => 'WARNING',
E_PARSE => 'PARSE',
E_NOTICE => 'NOTICE',
E_CORE_ERROR => 'CORE_ERROR',
E_CORE_WARNING => 'CORE_WARNING',
E_COMPILE_ERROR => 'COMPILE_ERROR',
E_COMPILE_WARNING => 'COMPILE_WARNING',
E_USER_ERROR => 'USER_ERROR',
E_USER_WARNING => 'USER_WARNING',
E_USER_NOTICE => 'USER_NOTICE',
E_STRICT => 'STRICT',
E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
E_DEPRECATED => 'DEPRECATED',
E_USER_DEPRECATED => 'USER_DEPRECATED',
];
$name = $errNames[$errno] ?? 'UNKNOWN_' . $errno;
// 3. 構建日誌上下文 (不暴露絕對路徑)
$log = [
'time' => date('c'),
'level' => $name,
'message' => $errstr,
'file' => basename($errfile), // 僅文件名
'line' => $errline,
'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5), // 限制深度
'url' => $_SERVER['REQUEST_URI'] ?? 'cli',
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'cli',
];
// 4. 寫入日誌系統 (Monolog/文件/syslog)
error_log(json_encode($log, JSON_UNESCAPED_UNICODE) . "\n", 3, '/var/log/php_errors.log');
// 5. 生產環境:不向用戶顯示任何技術細節
if (ini_get('display_errors')) {
// 開發環境可顯示友好錯誤頁
if ($errno & (E_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR)) {
http_response_code(500);
include __DIR__ . '/templates/500.php';
exit;
}
}
// 6. 返回 true 表示「已處理」,阻止標準處理器繼續
return true;
}
// 註冊
set_error_handler('productionErrorHandler');
?>
set_error_handler 無法捕獲 E_ERROR (致命錯誤),需配合關閉函數:
<?php
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
// 模擬觸發用戶錯誤以便統一日誌邏輯
// 注意:此處無法使用 set_error_handler 回調,需直接處理
$log = [
'time' => date('c'),
'level' => 'FATAL',
'message' => $error['message'],
'file' => basename($error['file']),
'line' => $error['line'],
];
error_log(json_encode($log) . "\n", 3, '/var/log/php_fatal.log');
// 返回友好頁面 (若輸出未發送)
if (!headers_sent()) {
http_response_code(500);
include __DIR__ . '/templates/500.php';
}
}
});
?>
<?php
// 僅對管理員 IP 或特定 Header 顯示詳細錯誤
function devErrorHandler($errno, $errstr, $errfile, $errline) {
$isAdmin = in_array($_SERVER['REMOTE_ADDR'] ?? '', ['192.168.1.100', '10.0.0.1'])
|| ($_SERVER['HTTP_X_DEBUG_KEY'] ?? '') === 'secret_key';
if ($isAdmin) {
// 顯示完整錯誤信息 (配合 xdebug.var_display_max_depth 等)
echo "";
echo "[$errno] $errstr\n";
echo "File: $errfile (line $errline)\n";
echo "Trace:\n";
debug_print_backtrace();
echo "";
} else {
// 普通用戶見友好頁
if ($errno & (E_ERROR | E_USER_ERROR)) {
http_response_code(500);
include '500.html';
exit;
}
}
return true; // 阻止標準輸出
}
// 根據環境變量切換
if (getenv('APP_ENV') === 'production') {
set_error_handler('productionErrorHandler');
} else {
set_error_handler('devErrorHandler');
}
?>
<?php
class ErrorException extends ErrorException {} // PHP 內建 ErrorException 已足夠
set_error_handler(function($errno, $errstr, $errfile, $errline) {
if (!(error_reporting() & $errno)) return false;
// 只將警告以上級別轉為異常
if ($errno & (E_WARNING | E_NOTICE | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE | E_RECOVERABLE_ERROR)) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
}
return true;
});
try {
$fp = fopen('not_exist.txt', 'r'); // 觸發 E_WARNING
} catch (ErrorException $e) {
// 統一異常處理邏輯
logError($e);
// 返回 API 錯誤響應或渲染錯誤頁
}
?>
display_errors = Off,log_errors = On,error_log 指定專用文件。