妥善的錯誤與異常處理能提升使用者體驗、加速除錯、降低安全風險並增強系統健壯性。本文系統梳理 PHP 的錯誤處理機制、自定義錯誤處理器、錯誤日誌記錄與異常處理最佳實踐。
| 值 | 常數 | 說明 | 備註 |
|---|---|---|---|
| 1 | E_ERROR | 致命執行錯誤,腳本終止 | 不可被自定義處理器捕獲 |
| 2 | E_WARNING | 執行警告,腳本繼續 | |
| 4 | E_PARSE | 編譯期語法解析錯誤 | |
| 8 | E_NOTICE | 執行期通知(如未定義變數) | |
| 16 | E_CORE_ERROR | PHP 啟動致命錯誤 | PHP 4+ |
| 32 | E_CORE_WARNING | PHP 啟動警告 | PHP 4+ |
| 64 | E_COMPILE_ERROR | 編譯致命錯誤 | PHP 4+ |
| 128 | E_COMPILE_WARNING | 編譯警告 | PHP 4+ |
| 256 | E_USER_ERROR | 用戶觸發致命錯誤 | PHP 4+,trigger_error() |
| 512 | E_USER_WARNING | 用戶觸發警告 | PHP 4+ |
| 1024 | E_USER_NOTICE | 用戶觸發通知 | PHP 4+ |
| 2048 | E_STRICT | 程式碼改進建議 | PHP 5+ |
| 4096 | E_RECOVERABLE_ERROR | 可捕獲的致命錯誤 | PHP 5.2+,可被自定義處理器捕獲 |
| 8192 | E_DEPRECATED | 棄用警告 | PHP 5.3+ |
| 16384 | E_USER_DEPRECATED | 用戶觸發棄用警告 | PHP 5.3+ |
| 32767 | E_ALL | 所有錯誤與警告 | PHP 5.3+ 值為 32767 |
關鍵限制:E_ERROR、E_PARSE、E_CORE_ERROR、E_CORE_WARNING、E_COMPILE_ERROR、E_COMPILE_WARNING及部分E_STRICT無法被set_error_handler()捕獲。
$conn = mysqli_connect('localhost', 'user', 'pass', 'db')
or die('資料庫連線失敗:' . mysqli_connect_error());
缺點:直接終止,無法統一格式、記錄日誌、優雅降級。
function customErrorHandler($errno, $errstr, $errfile, $errline, $errcontext) {
// 格式化輸出
$msg = "[{$errno}] {$errstr} in {$errfile}:{$errline}";
error_log($msg . "\n", 3, __DIR__ . '/logs/php_errors.log');
// 開發環境顯示
if (ini_get('display_errors')) {
echo "{$msg}";
}
// 不要對 E_RECOVERABLE_ERROR 返回 true,讓腳本繼續或拋出異常
return true; // 表示已處理,不再由內建處理器處理
}
// 接管 E_ALL & ~E_STRICT(不含無法捕獲的類型)
set_error_handler('customErrorHandler', E_ALL & ~E_STRICT);
if ($age < 0) {
trigger_error("年齡不能為負數:{$age}", E_USER_WARNING);
}
// 寫入系統日誌(syslog/event log)
error_log("資料庫連線失敗:{$err}", 0);
// 寫入自定義檔案
error_log("[" . date('c') . "] {$err}\n", 3, '/var/log/app/db_errors.log');
// 發送郵件給管理員
error_log("嚴重錯誤:{$err}", 1, 'admin@example.com', 'From: errors@example.com');
PHP 5 引入物件導向異常機制,適用於可預期、可恢復的業務邏輯錯誤。
try {
// 可能拋出異常的代碼
$user = User::findById($id);
if (!$user) {
throw new Exception("用戶不存在:{$id}", 404);
}
} catch (Exception $e) {
// 捕獲並處理
error_log($e->getMessage());
// 回應使用者
http_response_code($e->getCode() ?: 500);
echo json_encode(['error' => $e->getMessage()]);
} finally {
// 清理資源(關閉連線、釋放鎖)
$db->close();
}
try {
// ...
} catch (ValidationException $e) {
// 業務驗證失敗
http_response_code(422);
echo $e->getMessage();
} catch (DatabaseException $e) {
// 資料庫錯誤,記錄詳細資訊後拋出通用異常
error_log($e->getTraceAsString());
throw new Exception('系統暫時無法使用,請稍後再試', 503);
} catch (Exception $e) {
// 兜底
error_log($e->getMessage());
http_response_code(500);
echo '伺服器內部錯誤';
}
class ValidationException extends Exception {}
class DatabaseException extends Exception {}
class NotFoundException extends Exception {}
function globalExceptionHandler(Throwable $e) {
http_response_code(500);
error_log("[UNCAUGHT] {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}");
if (ini_get('display_errors')) {
echo "系統錯誤
{$e->getMessage()}
";
} else {
echo "服務暫時不可用
請稍後再試
";
}
}
set_exception_handler('globalExceptionHandler');
display_errors=On、error_reporting=E_ALL、使用 set_error_handler + set_exception_handler 統一格式化輸出。display_errors=Off、log_errors=On、錯誤寫入檔案/系統日誌/ELK,關鍵錯誤發送告警(Email/Slack/DingTalk)。ValidationException 等),在控制器/中介層統一捕獲轉為標準 API 回應。finally 或 register_shutdown_function 確保連線、鎖、臨時檔案釋放。echo $e->getTraceAsString() 給使用者看。建立一套專案統一的錯誤/異常處理骨架,能讓團隊專注於業務邏輯,同時保障系統可觀測性與安全性。