PHP 5 引入類似 Java/C++ 的物件導向異常處理模型,提供 try、catch、throw 關鍵字,讓錯誤流程控制更結構化、可維護。本文系統整理內建 Exception 類別結構、語法規則、多重捕獲與自訂異常最佳實務。
| 屬性/方法 | 存取層級 | 說明 |
|---|---|---|
$message | protected | 異常訊息字串 |
$code | protected | 使用者自訂錯誤代碼 (int) |
$file | protected | 拋出異常的檔案名稱 |
$line | protected | 拋出異常的程式碼行號 |
__construct($msg, $code) | public | 建構子,可選參數 |
getMessage() | final public | 回傳異常訊息 |
getCode() | final public | 回傳錯誤代碼 |
getFile() | final public | 回傳檔案名稱 |
getLine() | final public | 回傳行號 |
getTrace() | final public | 回傳回溯陣列 |
getTraceAsString() | final public | 回傳格式化回溯字串 |
__toString() | public | 可覆寫,回傳字串表現 |
<?php
// 1. try-catch 基本捕獲
try {
// 高風險操作:資料庫連線、檔案讀寫、API 呼叫
$db = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
// 捕獲特定異常類別
logError($e->getMessage());
showFriendlyMessage("服務暫時不可用,請稍後再試");
}
// 2. throw 主動拋出異常
function divide($a, $b) {
if ($b == 0) {
throw new Exception("除數不可為零", 1001); // 自訂代碼
}
return $a / $b;
}
try {
echo divide(10, 0);
} catch (Exception $e) {
echo "錯誤代碼: {$e->getCode()}"; // 1001
echo "錯誤訊息: {$e->getMessage()}"; // 除數不可為零
}
// 3. 多重 catch:依類別精準處理
try {
$pdo = new PDO($dsn, $user, $pass);
$stmt = $pdo->query("SELECT * FROM users");
} catch (PDOException $e) {
// 資料庫專屬錯誤
handleDbError($e);
} catch (Exception $e) {
// 其他通用錯誤
handleGenericError($e);
}
// 4. finally (PHP 5.5+):無論是否異常必執行
$fp = fopen("data.txt", "r");
try {
$data = fread($fp, filesize("data.txt"));
} finally {
fclose($fp); // 保證資源釋放
}
?>
<?php
// 自訂異常階層
class AppException extends Exception {}
class ValidationException extends AppException {}
class NotFoundException extends AppException {}
class DatabaseException extends AppException {}
// Service 層
class UserService {
private $pdo;
public function __construct(PDO $pdo) { $this->pdo = $pdo; }
public function register(string $email, string $password): int {
// 業務驗證拋出 ValidationException
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new ValidationException("Email 格式錯誤", 2001);
}
if (strlen($password) < 8) {
throw new ValidationException("密碼長度至少 8 碼", 2002);
}
try {
$stmt = $this->pdo->prepare("INSERT INTO users (email, password_hash) VALUES (?, ?)");
$stmt->execute([$email, password_hash($password, PASSWORD_DEFAULT)]);
return (int)$this->pdo->lastInsertId();
} catch (PDOException $e) {
// 封裝底層異常,拋出上層可理解的 DatabaseException
if ($e->getCode() == 23000) { // SQLSTATE 唯一鍵衝突
throw new ValidationException("Email 已被註冊", 2003);
}
throw new DatabaseException("資料庫錯誤: " . $e->getMessage(), 5001, $e);
}
}
}
// Controller 層
try {
$userId = (new UserService($pdo))->register($_POST['email'], $_POST['password']);
jsonResponse(['success' => true, 'user_id' => $userId]);
} catch (ValidationException $e) {
jsonResponse(['success' => false, 'code' => $e->getCode(), 'msg' => $e->getMessage()], 400);
} catch (DatabaseException $e) {
error_log($e->getTraceAsString()); // 記錄完整堆疊
jsonResponse(['success' => false, 'msg' => '系統繁忙,請稍後再試'], 500);
} catch (Exception $e) {
error_log("未預期錯誤: " . $e->getMessage());
jsonResponse(['success' => false, 'msg' => '未知錯誤'], 500);
}
?>
PDOException),拋出領域相關異常(DatabaseException),隱藏實作細節,符合「依賴倒置原則」。set_exception_handler(function($e) { ... }) 捕獲未被 catch 的異常,統一記錄日誌、顯示友善錯誤頁,避免暴露堆疊資訊。InvalidArgumentException、OutOfRangeException、RuntimeException 等標準異常,減少自訂類別數量。PHP 異常處理機制讓錯誤流程可控、可追蹤、可分層。善用 try-catch-finally 結構、自訂異常階層與全域處理器,能顯著提升程式碼健壯度與維護性。記住核心原則:拋出具體異常、捕獲精確類別、封裝底層細節、統一錯誤回應。