首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 異常處理機制深度解析:從入門到最佳實踐

     編輯:PHP 相關知識

核心認知:PHP 異常 ≠ 錯誤

這是理解 PHP 異常處理的關鍵分水嶺:

  • Error (錯誤): 引擎級故障,如語法錯誤、內存耗盡、除零警告。無法被 try-catch 捕獲 (PHP 7+ 部分可捕獲 Throwable),通常導致腳本終止。
  • Exception (異常): 業務邏輯層面的「非預期但可控狀態」,如驗證失敗、資源不存在、第三方 API 異常。**必須手動 throw**,配合 try-catch 處理。
關鍵點: PHP 不會自動將除零、文件不存在等運行錯誤轉為異常拋出!必須手動判斷並 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();
}
?>

Exception 類核心屬性與方法

屬性/方法說明訪問級別
$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+ Throwable 接口變革


// 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();
}

最佳實踐清單

  1. 區分業務異常與系統錯誤: 業務異常 (驗證失敗) 返回 4xx,系統錯誤 (DB 掛了) 返回 5xx。
  2. 異常信息雙語化: $message 給開發者看 (含上下文),用戶端返回友好提示。
  3. 錯誤碼標準化: 制定公司級錯誤碼規範 (如 1xxx 驗證、2xxx 業務衝突、5xxx 系統)。
  4. 異常鏈保留: throw new BizEx('msg', 100, $previous) 保留原始堆棧。
  5. 避免濫用: 正常流程控制 (如判斷空數組) 用 if-else,非預期失敗才用異常。
  6. 資源清理用 finally: 文件句柄、DB 連接、鎖必須在 finally 中釋放。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved