核心差異對照
| 特性 | Error (錯誤) | Exception (異常) |
| 嚴重性 | 通常為致命,腳本終止 | 可捕獲處理,程式可繼續 |
| 觸發來源 | 引擎內部 (語法、記憶體、核心) | 程式邏輯 (業務規則、資源不足) |
| 可否捕獲 | PHP 7+ 可用 Throwable 捕獲部分 | try-catch 完整捕獲 |
| 繼承體系 | Error (PHP 7) / 內部錯誤類型 | Exception 及子類 |
| 處理方式 | 錯誤處理器 set_error_handler() | 異常處理器 set_exception_handler() |
PHP 7+ 統一介面:Throwable
interface Throwable {
public function getMessage(): string;
public function getCode(): int;
public function getFile(): string;
public function getLine(): int;
public function getTrace(): array;
public function getTraceAsString(): string;
public function __toString(): string;
}
Error 實作 Throwable (致命錯誤、型別錯誤、解析錯誤等)
Exception 實作 Throwable
- 可用
catch (Throwable $e) 同時捕獲兩者
應用場景對比
場景一:資料庫連線失敗 → 拋異常
try {
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
// 正常邏輯
} catch (PDOException $e) {
log_error($e->getMessage());
show_user_friendly('服務暫時不可用,請稍後再試');
}
場景二:參數驗證失敗 → 拋異常
function transfer(string $from, string $to, float $amount): void
{
if ($amount <= 0) throw new InvalidArgumentException('金額必須大於 0');
if ($from === $to) throw new DomainException('不可轉給自己');
// ...
}
場景三:致命錯誤 (記憶體耗盡) → 無法捕獲 (PHP 7 前)
// PHP 7+ 可捕獲 Error
try {
$huge = str_repeat('x', 2_000_000_000); // 可能觸發 OutOfMemoryError
} catch (Error $e) {
// 處理或記錄
}
最佳實務
- **業務邏輯錯誤** → 拋
Exception 子類 (InvalidArgumentException, DomainException, RuntimeException)
- **可預期、可恢復** → 用異常
- **程式錯誤 (Bug)** → 讓它拋出 Error/Exception 並記錄,不要 try-catch 掩蓋
- **統一入口** 設定
set_exception_handler、set_error_handler 記錄日誌、回傳友善錯誤頁面/JSON
- **不要用異常控制流程** (如陣列遍歷結束拋異常),效能差且語意不清
自訂異常類別範例
class BusinessException extends Exception {
public function __construct(string $message, int $code = 0, ?Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
}
class InsufficientBalanceException extends BusinessException {}
class UserNotFoundException extends BusinessException {}