PHP 5 引入物件導向異常模型,PHP 7 擴展為 Throwable 介面統一 Exception 與 Error,PHP 8 增強語法彈性。掌握完整體系是編寫健壯程式的基礎。
Throwable (介面, PHP 7+)
├── Exception (使用者程式異常)
│ ├── LogicException (邏輯錯誤)
│ │ ├── InvalidArgumentException
│ │ ├── LengthException
│ │ └── DomainException
│ ├── RuntimeException (執行期錯誤)
│ │ ├── OutOfBoundsException
│ │ ├── OverflowException
│ │ └── UnexpectedValueException
│ └── ... (其他 SPL 異常)
└── Error (引擎內部錯誤, PHP 7+)
├── TypeError (型別錯誤)
├── ArgumentCountError (參數數量錯誤)
├── ArithmeticError (數學錯誤)
│ └── DivisionByZeroError
├── AssertionError (斷言失敗)
└── ParseError (解析錯誤)
<?php
try {
// 可能拋出異常的程式碼
$result = riskyOperation($input);
} catch (SpecificException $e) {
// 處理特定異常
handleSpecific($e);
} catch (AnotherException $e) {
// 處理另一類異常
} catch (Throwable $e) {
// 捕獲所有 Exception 與 Error (PHP 7+)
// 建議僅用於最外層或日誌記錄
} finally {
// 無論是否發生異常都會執行
// 適用於資源清理:關閉連線、釋放鎖、關閉檔案
cleanup();
}
?>
| 屬性/方法 | 說明 | 範例輸出 |
|---|---|---|
getMessage() | 異常訊息 | "Division by zero" |
getCode() | 自定義錯誤碼 | 404 |
getFile() | 發生異常的檔案路徑 | "/var/www/app/Service.php" |
getLine() | 發生異常的行號 | 42 |
getTrace() | 堆疊追蹤陣列 | [['file'=>..., 'line'=>..., 'function'=>...], ...] |
getTraceAsString() | 格式化堆疊字串 | "#0 /app/Service.php(42): ...\n#1 ..." |
getPrevious() | 前一個異常 (鏈式) | Exception 物件或 null |
__toString() | 完整字串表示 | "Exception: Division by zero in ..." |
<?php
try {
$pdo->query($sql);
} catch (PDOException $e) {
// 保留原始異常資訊,拋出業務層異常
throw new DatabaseException('查詢失敗', 0, $e);
}
?>
<?php
try {
// ...
} catch (ValidationException | AuthenticationException $e) {
// 同樣處理邏輯的異常可合併
http_response_code(400);
logError($e);
}
?>
<?php
try {
// ...
} catch (DatabaseException $e) if ($e->getCode() === 1205) {
// 僅捕獲鎖等待超時 (MySQL 錯誤碼 1205)
retryOperation();
} catch (DatabaseException $e) {
// 其他資料庫錯誤
}
?>
<?php
set_exception_handler(function (Throwable $e) {
http_response_code(500);
logError($e);
echo json_encode([
'error' => 'Internal Server Error',
'trace_id' => uniqid()
]);
});
// 注意:全域處理器無法捕獲:
// - E_ERROR / E_PARSE / E_CORE_ERROR 等致命錯誤 (需 register_shutdown_function)
// - 異常處理器內部拋出的異常
<?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])) {
// 將致命錯誤轉為異常處理
$e = new ErrorException(
$error['message'],
0,
$error['type'],
$error['file'],
$error['line']
);
// 交給全域處理器或直接處理
handleFatalError($e);
}
});
?>
| 錯誤做法 | 問題 | 正確做法 |
|---|---|---|
| catch (Exception $e) { } | 吞沒異常,導致 Bug 難以追蹤 | 至少記錄日誌,或重拋 throw $e |
| 用異常控制正常流程 | 效能差、語意不清 | 預期情況用回傳值/Optional/Result 物件 |
| catch 後不處理直接結束 | 資源洩漏 (連線、檔案、鎖) | 使用 finally 或 RAII 模式清理 |
| 拋出新異常未保留前因 | 丟失根因堆疊 | throw new NewException(msg, code, $previous) |
throw 運算式化:$x = $arr[$key] ?? throw new NotFoundException();catch 可省略變數:catch (InvalidArgumentException) { ... }ValueError、UnhandledMatchErrorStringable 介面支援 __toString() 型別提示異常處理不是錯誤的終點,而是程式韌性的起點。建立分層、分類、可追蹤的異常體系,配合統一的處理策略,能讓專案在面對不可預期情況時優雅降級、快速定位、持續演進。