PHP7中的異常與錯誤處理

PHP7中的異常與錯誤處理

PHP 中的 Exception, Error, Throwable

1.PHP 中將代碼自身異常(一般是環境或者語法非法所致)稱作錯誤 Error,將運行中出現的邏輯錯誤稱爲異常 Exception
2.錯誤是沒法通過代碼處理的,而異常則可以通過 try/catch 來處理
3.PHP7 中出現了 Throwable 接口,該接口由 Error 和 Exception 實現,用戶不能直接實現 Throwable 接口,而只能通過繼承 Exception 來實現接口

PHP7 異常處理機制

過去的 PHP,處理致命錯誤幾乎是不可能的。致命錯誤不會調用由 set_error_handler() 設置的處理方式,而是簡單的停止腳本的執行。

在 PHP7 中,當致命錯誤和可捕獲的錯誤(E_ERROR 和 E_RECOVERABLE_ERROR)發生時會拋出異常,而不是直接停止腳本的運行。對於某些情況,比如內存溢出,致命錯誤則仍然像之前一樣直接停止腳本執行。在 PHP7 中,一個未捕獲的異常也會是一個致命錯誤。這意味着在 PHP5.x 中致命錯誤拋出的異常未捕獲,在 PHP7 中也是致命錯誤。

注意:其他級別的錯誤如 warning 和 notice,和之前一樣不會拋出異常,只有 fatal 和 recoverable 級別的錯誤會拋出異常。

從 fatal 和 recoverable 級別錯誤拋出的異常並非繼承自 Exception 類。這種分離是爲了防止現有 PHP5.x 的用於停止腳本運行的代碼也捕獲到錯誤拋出的異常。fatal 和 recoverable 級別的錯誤拋出的異常是一個全新分離出來的類 Error 類的實例。跟其他異常一樣,Error 類異常也能被捕獲和處理,同樣允許在 finally 之類的塊結構中運行。

Throwable

爲了統一兩個異常分支,Exception 和 Error 都實現了一個全新的接口:Throwable

PHP7 中新的異常結構如下:

interface Throwable
    |- Exception implements Throwable
        |- ...
    |- Error implements Throwable
        |- TypeError extends Error
        |- ParseError extends Error
        |- ArithmeticError extends Error
            |- DivisionByZeroError extends ArithmeticError
        |- AssertionError extends Error

如果在 PHP7 的代碼中定義了 Throwable 類,它將會是如下這樣:

interface Throwable{
    public function getMessage(): string;
    public function getCode(): int;
    public function getFile(): string;
    public function getLine(): int;
    public function getTrace(): array;
    public function getTraceAsString(): string;
    public function getPrevious(): Throwable;
    public function __toString(): string;
}

這個接口看起來很熟悉。Throwable 規定的方法跟 Exception 幾乎是一樣的。唯一不同的是 Throwable::getPrevious() 返回的是 Throwable 的實例而不是 Exception 的。Exception 和 Error 的構造函數跟之前 Exception 一樣,可以接受任何 Throwable 的實例。
**
Throwable 可以用於 try/catch塊中捕獲 Exception 和 Error 對象(或是任何未來可能的異常類型)。記住捕獲更多特定類型的異常並且對之做相應的處理是更好的實踐。然而在某種情況下我們想捕獲任何類型的異常(比如日誌或框架中錯誤處理)。在 PHP7 中,要捕獲所有的應該使用 Throwable 而不是 Exception。

try {
// Code that may throw an Exception or Error.
} catch (Throwable $t) {
// Handle exception
}

用戶定義的類不能實現 Throwable 接口。做出這個決定一定程度上是爲了預測性和一致性——只有 Exception 和 Error 的對象可以被拋出。此外,異常需要攜帶對象在追溯堆棧中創建位置的信息,而用戶定義的對象不會自動的有參數來存儲這些信息。

Throwable 可以被繼承從而創建特定的包接口或者添加額外的方法。一個繼承自 Throwable 的接口只能被 Exception 或 Error 的子類來實現。

interface MyPackageThrowable extends Throwable {}

class MyPackageException extends Exception implements MyPackageThrowable {}

throw new MyPackageException();

Error

事實上,PHP5.x 中所有的錯誤都是 fatal 或 recoverable 級別的錯誤,在 PHP7 中都能拋出一個 Error實例。跟其他任何異常一樣,Error 對象可以使用 try/catch 塊來捕獲。

$var = 1;
try {
$var->method(); // Throws an Error object in PHP 7.
} catch (Error $e) {
// Handle error
}

通常情況下,之前的致命錯誤都會拋出一個基本的 Error 類實例,但某些錯誤會拋出一個更具體的 Error 子類:TypeError、ParseError 以及 AssertionError。

TypeEror

當函數參數或返回值不符合聲明的類型時,TypeError 的實例會被拋出。

function add(int $left, int $right){
return $left + $right;
}

try {
$value = add('left', 'right');
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}

//Argument 1 passed to add() must be of the type integer, string given

ParseError

當 include/require 文件或 eval() 代碼存在語法錯誤時,ParseError 會被拋出。

try {
require 'file-with-parse-error.php';
} catch (ParseError $e) {
echo $e->getMessage(), "\n";
}

ArithmeticError

ArithmeticError 在兩種情況下會被拋出。一是位移操作負數位。二是調用intdiv() 時分子是 PHP_INT_MIN 且分母是 -1 (這個使用除法運算符的表達式:PHP_INT_MIN / -1,結果是浮點型)。

try {
$value = 1 << -1;
catch (ArithmeticError $e) {
echo $e->getMessage();//Bit shift by negative number
}

DevisionByZeroError

當 intdiv() 的分母是 0 或者取模操作 (%) 中分母是 0 時,DivisionByZeroError 會被拋出。注意在除法運算符 (/) 中使用 0 作除數(也即xxx/0這樣寫)時只會觸發一個 warning,這時候若分子非零結果是 INF,若分子是 0 結果是 NaN。

try {
$value = 1 % 0;
} catch (DivisionByZeroError $e) {
echo $e->getMessage();//Modulo by zero
}

AssertionError

當 assert() 的條件不滿足時,AssertionError 會被拋出。

ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);

$test = 1;

assert($test === 0);

//Fatal error: Uncaught AssertionError: assert($test === 0)

只有斷言啓用並且是設置 ini 配置的 zend.assertions = 1 和 assert.exception = 1 時,assert()纔會執行並拋 AssertionError。

在你的代碼中使用 Error

用戶可以通過繼承 Error 來創建符合自己層級要求的 Error 類。這就形成了一個問題:什麼情況下應該拋出 Exception,什麼情況下應該拋出 Error。

Error 應該用來表示需要程序員關注的代碼問題。從 PHP 引擎拋出的 Error 對象屬於這些分類,通常都是代碼級別的錯誤,比如傳遞了錯誤類型的參數給一個函數或者解析一個文件發生錯誤。Exception 則應該用於在運行時能安全的處理,並且另一個動作能繼續執行的情況。

由於 Error 對象不應該在運行時被處理,因此捕獲 Error 對象也應該是不頻繁的。一般來說,Error 對象僅被捕獲用於日誌記錄、執行必要的清理以及展示錯誤信息給用戶。

編寫代碼支持 PHP5.x 和 PHP7 的異常

爲了在同樣的代碼中捕獲任何 PHP5.x 和 PHP7 的異常,可以使用多個 catch,先捕獲 Throwable,然後是 Exception。當 PHP5.x 不再需要支持時,捕獲 Exception 的 catch 塊可以移除。

try {
// Code that may throw an Exception or Error.
} catch (Throwable $t) {
// Executed only in PHP 7, will not match in PHP 5.x
} catch (Exception $e) {
// Executed only in PHP 5.x, will not be reached in PHP 7
}

不幸的是,處理異常的函數中的類型聲明不容易確定。當 Exception 用於函數參數類型聲明時,如果函數調用時候能用 Error 的實例,這個類型聲明就要去掉。當 PHP5.x 不需要被支持時,類型聲明則可以還原爲 Throwable。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章