PHP7中的異常與錯誤處理


PHP 中的 Exception, Error, Throwable

  • PHP 中將代碼自身異常(一般是環境或者語法非法所致)稱作錯誤 Error,將運行中出現的邏輯錯誤稱為異常 Exception
  • 錯誤是沒法通過代碼處理的,而異常則可以通過 try/catch 來處理
  • 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 中新的異常結構如下:

1 interface Throwable
2     |- Exception implements Throwable
3         |- ...
4     |- Error implements Throwable
5         |- TypeError extends Error
6         |- ParseError extends Error
7         |- ArithmeticError extends Error
8             |- DivisionByZeroError extends ArithmeticError
9         |- AssertionError extends Error

 

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

 1 interface Throwable{
 2     public function getMessage(): string;
 3     public function getCode(): int;
 4     public function getFile(): string;
 5     public function getLine(): int;
 6     public function getTrace(): array;
 7     public function getTraceAsString(): string;
 8     public function getPrevious(): Throwable;
 9     public function __toString(): string;
10 }

 

這個接口看起來很熟悉。Throwable 規定的方法跟 Exception 幾乎是一樣的。唯一不同的是 Throwable::getPrevious() 返回的是 Throwable 的實例而不是 Exception 的。Exception 和 Error 的構造函數跟之前 Exception 一樣,可以接受任何 Throwable 的實例。

Throwable 可以用於 try/catch塊中捕獲 Exception 和 Error 對象(或是任何未來可能的異常類型)。記住捕獲更多特定類型的異常並且對之做相應的處理是更好的實踐。然而在某種情況下我們想捕獲任何類型的異常(比如日志或框架中錯誤處理)。在 PHP7 中,要捕獲所有的應該使用 Throwable 而不是 Exception

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

 

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

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

1 interface MyPackageThrowable extends Throwable {}
2 
3 class MyPackageException extends Exception implements MyPackageThrowable {}
4 
5 throw new MyPackageException();

 

Error

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

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

 

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

TypeError

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

 1 function add(int $left, int $right){
 2 return $left + $right;
 3 }
 4 
 5 try {
 6 $value = add('left', 'right');
 7 } catch (TypeError $e) {
 8 echo $e->getMessage(), "\n";
 9 }
10 
11 //Argument 1 passed to add() must be of the type integer, string given

 

ParseError

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

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

 

ArithmeticError

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

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

 

DevisionByZeroError

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

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

 

AssertionError

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

ini_set('zend.assertions', 1);
1 ini_set('assert.exception', 1);
2 
3 $test = 1;
4 
5 assert($test === 0);
6 
7 //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 塊可以移除。

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

 

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM