PHP 核心 - 異常處理

心智極客發表於2019-11-22

錯誤與異常

在 PHP 7 之前,錯誤處理和異常處理是分開的。

try {
    $a = 5 % 0;
} catch (Exception $e) {
    echo $e->getMessage();
    $a = -1;  // 通過異常來處理 $a 為 0 的情況,但是實際上,捕獲不到該異常
}

echo $a;  // 無法執行

PHP 7 開始,錯誤也可以像異常那樣丟擲。

try {
    $a = 5 % 0;
    // 注意,DivisionByZeroError 錯誤只能捕捉到 % 運算,無法捕捉 / 運算
} catch (DivisionByZeroError $e) {
    echo $e->getMessage();
    $a = -1;  
}

echo $a; // -1

相關類

PHP 中的全部異常和錯誤

Throwable - 介面
    Error - 所有錯誤的基類
       ArithmeticError
          DivisionByZeroError
       AssertionError
       CompileError
          ParseError
       TypeError
          ArgumentCountError
    Exception - 所有異常的基類
       ClosedGeneratorException
       DOMException
       ErrorException
       IntlException
       JsonException
       LogicException
          BadFunctionCallException
             BadMethodCallException
          DomainException
          InvalidArgumentException
          LengthException
          OutOfRangeException
       PharException
       ReflectionException
       RuntimeException
          OutOfBoundsException
          OverflowException
          PDOException
          RangeException
          UnderflowException
          UnexpectedValueException
       SodiumException

Throwable 介面

Throwable {
    abstract public getMessage ( void ) : string
    abstract public getCode ( void ) : int
    abstract public getFile ( void ) : string
    abstract public getLine ( void ) : int
    abstract public getTrace ( void ) : array
    abstract public getTraceAsString ( void ) : string
    abstract public getPrevious ( void ) : Throwable
    abstract public __toString ( void ) : string
}

Error 基類

Error implements Throwable {

    protected string $message ;
    protected int $code ;
    protected string $file ;
    protected int $line ;

    public __construct ([ string $message = "" [, int $code = 0 [, Throwable $previous = NULL ]]] )
    final public getMessage ( void ) : string
    final public getPrevious ( void ) : Throwable
    final public getCode ( void ) : mixed
    final public getFile ( void ) : string
    final public getLine ( void ) : int
    final public getTrace ( void ) : array
    final public getTraceAsString ( void ) : string
    public __toString ( void ) : string
    final private __clone ( void ) : void
}

Exception 基類

Exception {

    protected string $message ;
    protected int $code ;
    protected string $file ;
    protected int $line ;

    public __construct ([ string $message = "" [, int $code = 0 [, Throwable $previous = NULL ]]] )
    final public getMessage ( void ) : string
    final public getPrevious ( void ) : Throwable
    final public getCode ( void ) : int
    final public getFile ( void ) : string
    final public getLine ( void ) : int
    final public getTrace ( void ) : array
    final public getTraceAsString ( void ) : string
    public __toString ( void ) : string
    final private __clone ( void ) : void
}

除此之外,使用者也可以自定義異常

class ModelNotFoundException extends \RuntimeException
{

    protected $model;

    public function setModel($model)
    {
        $this->model = $model;
        $this->message = "No query results for model [{$model}]";
        return $this;
    }
}

// 使用
throw (new ModelNotFoundException)->setModel(get_class($this->model));

自定義異常時,應當以具體錯誤來命名,而不是從釋出者的角度來命名

// 不好的命名
CouldNotCopyFileException

// 好的命名
FileNotFoundException

注意,使用者無法實現 Throwable 介面,只能通過繼承 ErrorException 來實現。

//  報錯:Class MyException cannot implement interface Throwable, extend Exception or Error instead
class MyException implements Throwable {}

// 正確
class MyException extends Exception implements Throwable {}

也可以這麼操作

interface MyThrowable extends Throwable {
    public function myCustomMethod();
}

class MyException extends Exception implements MyThrowable {
    public function myCustomMethod()
    {
        // implement custom method code
    }
}

使用場景

異常將錯誤處理與常規程式碼進行分離,能夠讓業務流程更加清晰。

try {
    // 業務流程
} catch (FileNotFoundException $e) {

} catch (FileTypeException $e) {

} catch (Exception $e) {

}

如果沒有使用異常,那麼在進行業務流程時就必須考慮各種錯誤處理,會讓程式碼的邏輯變得混亂

// 業務流程
// 錯誤處理
// 業務流程
// 錯誤處理

方法檢測到錯誤卻沒有足夠的資訊來進行處理,這時候應該丟擲丟擲異常,讓客戶端來處理異常,有利於保持職責的單一性。

public function foo()
{
    if(條件不滿足){
        // 丟擲異常,不進行處理,讓客戶端進行處理
    }
}

異常機制有利於保持資料一致性很重要,在資料一致性可能被破壞時,就需要使用異常機制進行事後補救。

try {

} cache(Exception $e){
    // 執行一些補救措施
} 

異常是層層丟擲的,客戶端只需要在合適的時候處理特定的異常即可,不需要一次性處理所有異常。

// 只處理 404 異常
public function actionType($username)
{
    try {
        $user = $client->get(sprintf("/api/user/%s", $username));
    } catch (RequestException $e) {
        if (404 === $e->getResponse()->getStatusCode()) {
            return "create";
        }

        throw $e;
    }

    return "update";
}

為了不洩露敏感資訊,需要一個全域性異常處理程式。

set_exception_handler(function($exception){
    echo '發生了未知錯誤';
    log($exception->getMessage());
});

點選 連結,免費加入心智極客的知識星球分享群,共同成長。