這是理解 PHP 異常處理的關鍵分水嶺:
try-catch 捕獲 (PHP 7+ 部分可捕獲 Throwable),通常導致腳本終止。throw**,配合 try-catch 處理。throw new Exception()。
<?php
try {
// 高風險業務代碼
$result = riskyOperation($input);
// 手動拋出業務異常
if ($result === false) {
throw new Exception('業務處理失敗: ' . getErrorMessage(), 1001);
}
return $result;
} catch (Exception $e) {
// 異常處理:記錄、通知、降級、友好提示
logError($e->getMessage(), $e->getCode(), $e->getTraceAsString());
notifyAdmin($e);
// 返回用戶友好響應
showUserMessage('系統繁忙,請稍後重試 (錯誤碼: ' . $e->getCode() . ')');
} finally {
// PHP 5.5+ 支援:清理資源 (關閉連接、釋放鎖)
cleanupResources();
}
?>
| 屬性/方法 | 說明 | 訪問級別 |
|---|---|---|
$message | 異常描述信息 | protected |
$code | 自定義錯誤碼 (int) | protected |
$file | 拋出異常的文件路徑 | protected |
$line | 拋出異常的行號 | protected |
getMessage() | 獲取信息 | public final |
getCode() | 獲取錯誤碼 | public final |
getFile() | 獲取文件 | public final |
getLine() | 獲取行號 | public final |
getTrace() | 堆棧跟蹤數組 | public final |
getTraceAsString() | 格式化堆棧字串 | public final |
__toString() | 字符串化輸出 (可重寫) | public |
建立異常層級結構,便於精準捕獲與差異化處理:
<?php
// 基礎業務異常
abstract class BusinessException extends Exception {
public function __construct(string $message = "", int $code = 0, Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
public function __toString(): string {
return sprintf("[%s:%d] %s @ %s:%d",
get_class($this), $this->code, $this->message, $this->file, $this->line);
}
}
// 具體業務異常
class ValidationException extends BusinessException {} // 參數驗證失敗 (400)
class NotFoundException extends BusinessException {} // 資源不存在 (404)
class UnauthorizedException extends BusinessException {} // 未授權 (401)
class ConflictException extends BusinessException {} // 資源衝突 (409)
class ServiceUnavailableException extends BusinessException {} // 依賴服務不可用 (503)
<?php
function registerUser(array $data): User {
try {
// 1. 輸入驗證層
if (empty($data['email'])) throw new ValidationException('郵箱不能為空', 1001);
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) throw new ValidationException('郵箱格式無效', 1002);
if (strlen($data['password']) < 8) throw new ValidationException('密碼長度不足 8 位', 1003);
if ($data['password'] !== $data['password_confirm']) throw new ValidationException('兩次密碼不一致', 1004);
if (strlen($data['username']) > 16) throw new ValidationException('用戶名過長', 1005);
// 2. 業務邏輯層 (可能拋出其他異常)
if (UserRepository::emailExists($data['email'])) {
throw new ConflictException('郵箱已被註冊', 2001);
}
// 3. 數據持久化 (捕獲底層 PDOException 轉換)
try {
$user = UserRepository::create($data);
} catch (PDOException $e) {
// 將底層異常包裝為業務異常,隱藏實現細節
throw new ServiceUnavailableException('註冊服務暫時不可用', 5003, $e);
}
return $user;
} catch (BusinessException $e) {
// 記錄業務異常 (非系統錯誤,級別為 Notice/Info)
Log::notice($e->__toString());
throw $e; // 重新拋出,讓上層 Controller 統一處理 HTTP 響應
}
}
// Controller 層統一異常處理
try {
$user = registerUser($_POST);
jsonSuccess(['user_id' => $user->id]);
} catch (ValidationException $e) {
jsonError($e->getMessage(), 400, $e->getCode());
} catch (ConflictException $e) {
jsonError($e->getMessage(), 409, $e->getCode());
} catch (ServiceUnavailableException $e) {
Log::error($e->getTraceAsString()); // 系統級錯誤需 Error 級別
jsonError('服務繁忙', 503, $e->getCode());
} catch (Exception $e) {
Log::critical('未捕獲異常: ' . $e->__toString());
jsonError('系統內部錯誤', 500, 9999);
}
<?php
// 僅捕獲未被 catch 的異常
set_exception_handler(function (Throwable $e) {
// 生產環境不輸出堆棧細節
if (ini_get('display_errors')) {
echo "系統錯誤
" . htmlspecialchars($e->__toString()) . "
";
} else {
Log::emergency('Uncaught: ' . $e->__toString());
echo "服務暫時不可用
我們已記錄該錯誤,請稍後重試。
";
}
});
// 註冊致命錯誤處理 (PHP 7+ Throwable 捕獲大多數)
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']);
// 調用上述 handler 邏輯...
}
});
// PHP 7 統一異常體系
// Exception 實現 Throwable
// Error 實現 Throwable (原 Fatal Error 現在可 catch)
// 現在可以捕獲 TypeError, ParseError, ArithmeticError 等
try {
$val = 10 / 0; // 拋出 DivisionByZeroError (繼承 ArithmeticError -> Error -> Throwable)
} catch (DivisionByZeroError $e) { // 精確捕獲
echo "除零錯誤: " . $e->getMessage();
} catch (Throwable $e) { // 兜底捕獲所有
echo "其他錯誤: " . $e->getMessage();
}
$message 給開發者看 (含上下文),用戶端返回友好提示。throw new BizEx('msg', 100, $previous) 保留原始堆棧。if-else,非預期失敗才用異常。