PHP 例外處理 (Exception)
例外處理是現代 PHP 中處理錯誤的標準方式。當程式執行過程中發生問題(例如檔案不存在、資料庫連線失敗、輸入驗證錯誤等),可以「拋出」一個例外,然後在適當的地方「捕獲」並處理它。這種機制讓錯誤處理程式碼與正常邏輯分離,使程式碼更清晰、更易維護。
try-catch 基本用法
try 區塊包含可能發生錯誤的程式碼,catch 區塊負責處理捕獲到的例外:
<?php
try {
// 可能發生錯誤的程式碼
$file = file_get_contents('nonexistent.txt');
if ($file === false) {
throw new Exception('檔案讀取失敗');
}
} catch (Exception $e) {
echo "錯誤:" . $e->getMessage();
}
?>
Exception 物件
Exception 物件包含例外的詳細資訊,可以透過以下方法取得:
<?php
try {
throw new Exception('發生錯誤', 100);
} catch (Exception $e) {
echo $e->getMessage(); // 發生錯誤
echo $e->getCode(); // 100
echo $e->getFile(); // 檔案路徑
echo $e->getLine(); // 行號
echo $e->getTraceAsString(); // 堆疊追蹤
}
?>
finally 區塊
finally 區塊中的程式碼無論是否發生例外都會執行,通常用於清理資源(如關閉檔案、釋放連線等):
<?php
$handle = null;
try {
$handle = fopen('data.txt', 'r');
// 處理檔案...
} catch (Exception $e) {
echo "錯誤:" . $e->getMessage();
} finally {
// 無論是否發生例外都會執行
if ($handle) {
fclose($handle);
}
}
?>
多重 catch
可以使用多個 catch 區塊來處理不同類型的例外。PHP 會依序檢查,第一個符合的 catch 會被執行:
<?php
try {
// ...
} catch (InvalidArgumentException $e) {
echo "參數錯誤:" . $e->getMessage();
} catch (RuntimeException $e) {
echo "執行錯誤:" . $e->getMessage();
} catch (Exception $e) {
echo "其他錯誤:" . $e->getMessage();
}
// PHP 7.1+ 可以合併
try {
// ...
} catch (InvalidArgumentException | RuntimeException $e) {
echo "錯誤:" . $e->getMessage();
}
?>
自訂例外
你可以建立繼承自 Exception 的自訂例外類別,來封裝特定的錯誤資訊和邏輯:
<?php
class ValidationException extends Exception {
private array $errors;
public function __construct(array $errors) {
parent::__construct('驗證失敗');
$this->errors = $errors;
}
public function getErrors(): array {
return $this->errors;
}
}
class DatabaseException extends Exception {
public function __construct(string $message, ?\Throwable $previous = null) {
parent::__construct($message, 0, $previous);
}
}
// 使用
try {
$errors = ['email' => 'Email 格式不正確'];
throw new ValidationException($errors);
} catch (ValidationException $e) {
foreach ($e->getErrors() as $field => $message) {
echo "$field: $message\n";
}
}
?>
重新拋出例外
有時你需要捕獲例外、進行一些處理(如記錄日誌),然後重新拋出讓上層程式碼處理。也可以用自訂例外包裝原始例外:
<?php
function processData($data) {
try {
// 處理資料
} catch (Exception $e) {
// 記錄後重新拋出
error_log($e->getMessage());
throw $e;
}
}
function processWithWrapper($data) {
try {
// 處理資料
} catch (PDOException $e) {
// 包裝成自訂例外
throw new DatabaseException('資料庫操作失敗', $e);
}
}
?>
全域例外處理
使用 set_exception_handler() 可以設定一個函數來處理所有未被捕獲的例外,這是最後的防線:
<?php
set_exception_handler(function (Throwable $e) {
error_log($e->getMessage());
http_response_code(500);
echo "系統發生錯誤";
});
// 未捕獲的例外會被這個處理器處理
throw new Exception('未捕獲的例外');
?>
SPL 例外類別
PHP 提供多種內建例外:
<?php
// 邏輯相關
throw new LogicException('邏輯錯誤');
throw new InvalidArgumentException('參數無效');
throw new OutOfRangeException('超出範圍');
// 執行時相關
throw new RuntimeException('執行時錯誤');
throw new UnexpectedValueException('非預期的值');
throw new OutOfBoundsException('索引超出邊界');
?>
實際應用
<?php
class UserService {
public function __construct(private PDO $db) {}
public function createUser(array $data): int {
$this->validate($data);
try {
$stmt = $this->db->prepare(
"INSERT INTO users (name, email) VALUES (?, ?)"
);
$stmt->execute([$data['name'], $data['email']]);
return (int) $this->db->lastInsertId();
} catch (PDOException $e) {
if ($e->getCode() === '23000') {
throw new ValidationException(['email' => 'Email 已被使用']);
}
throw new DatabaseException('建立使用者失敗', $e);
}
}
private function validate(array $data): void {
$errors = [];
if (empty($data['name'])) {
$errors['name'] = '名稱為必填';
}
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Email 格式不正確';
}
if (!empty($errors)) {
throw new ValidationException($errors);
}
}
}
?>