php面向对象异常处理,PHP 错误和异常处理(下)
PHP 錯誤和異常處理(下)
由 學院君 創建于9個月前, 最后更新于 7個月前
版本號 #1
1723 views
2 likes
0 collects
上篇我們講了 PHP 中的錯誤報告和捕獲,今天,我們來看看 PHP 程序中的異常處理。
錯誤 vs. 異常
錯誤與異??梢钥醋饕粚\生兄弟,從嚴格的面向對象編程角度來說,錯誤指的是致命錯誤(Fatal Error,比如編譯錯誤和語法錯誤),出現運行時錯誤后,程序應該無法繼續往后執行,需要執行一些清理工作并記錄日志后退出當前處理流程。
而異常指的是程序中出現的可預測的、可恢復的中輕度問題,比如數空對象引用、文件不存在、除數為零、數組越界等,當程序運行時出現異常后,我們可以對其進行捕獲,或者拋給上層的業務代碼處理,和錯誤報告類似,如果通過 set_exception_hanlder 函數定義了全局異常處理器,則所有未處理異常會集中到這里處理,如果沒有定義任何處理異常的代碼,最終會拋出一個 Fatal Error(也就是說,所有未處理異常都會被當作錯誤進行兜底處理)。程序出現異常后,應該可以繼續往后執行。
但是我們在 PHP 中可以看到兩者的邊界并不明顯,因為異常是 PHP 5 之后實現完整面向對象機制后引入的,之前的 PHP 中只有錯誤,沒有異常,所以你可以看到那么多的錯誤級別,比如 Notice、Warning、Deprecated 這些中輕度錯誤,實際上完全可以通過異常進行處理。
層次結構
在 PHP 7 中,所有錯誤都歸屬于 Error 類,所有異常都歸屬于 Exception 類,兩者是并列關系,并且最新 PHP 內置錯誤和異常類型如下表所示:
而 Error 和 Exception 類又都實現了 Throwable 接口。
異常處理
有了以上的了解,大家應該大體上明白了異常是怎么回事以及所處的位置,接下來,我們來看看如何處理異常,我們按照三個層級遞進:首先是在定義代碼的地方捕獲并處理,然后是在上層調用的地方捕獲并處理,以及定義全局異常處理器處理。
在 php_learning/oop 目錄下新建 exception.php 保存本篇教程的代碼。
捕獲異常
首先來看如何在代碼定義的地方捕獲異常,和錯誤捕獲一樣,我們可以 try...catch... 語句塊捕獲異常。
在 exception.php 中編寫一段測試代碼:
我們試圖從 $book 數組中訪問一個不存在的索引,此時沒有定義任何異常捕獲和處理邏輯,所以會以錯誤報告方式進行兜底處理:
現在我們在 getItemFromBook 方法中會參數進行驗證,如果不滿足要求則拋出異常:
function getItemFromBook($book, $key)
{
if (empty($book) || !key_exists($key, $book)) {
throw new InvalidArgumentException("數組為空或者對應索引不存在!");
}
return $book[$key];
}
通過 throw 關鍵字即可拋出異常,這里我們通過 new 關鍵字實例化了一個內置的 InvalidArgumentException 異常對象作為返回值拋出。
拋出異常后會終止后續代碼的執行,然后我們可以在調用的地方通過 try/catch 對這個異常進行捕獲:
try {
$val = getItemFromBook($book, 'desc');
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage();
exit();
}
var_dump($val);
其原理是當 try 語句塊中遇到異常后,會通過 catch 語句進行捕獲,如果拋出的異常和聲明異常類型匹配,則執行 catch 語句塊中的內容。這樣,當我們再次執行代碼時,就會捕獲這個異常:
如果你不知道拋出的異常類型是什么,可以通過 Exception 基類捕獲(或者其他父級異常類),也就是說,此處也符合父子類型的轉化邏輯:
try {
$val = getItemFromBook($book, 'desc');
} catch (Exception $exception) {
echo $exception->getMessage();
exit();
}
var_dump($val);
但是如果不是 InvalidArgumentException 或者其父類,就不能捕獲了:
try {
$val = getItemFromBook($book, 'desc');
} catch (RuntimeException $exception) {
echo $exception->getMessage();
exit();
}
var_dump($val);
執行上述代碼,打印結果如下:
未處理異常會轉化為 Fatal Error 處理。
如果調用程序拋出了多個異常:
function getItemFromBook($book, $key)
{
if (empty($book)) {
throw new InvalidArgumentException("數組為空!");
}
if (!key_exists($key, $book)) {
throw new OutOfBoundsException("對應索引不存在!");
}
return $book[$key];
}
可以通過多個 catch 語句進行捕獲:
try {
$val = getItemFromBook($book, 'desc');
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage();
exit();
} catch (OutOfBoundsException $exception) {
echo $exception->getMessage();
exit();
}
var_dump($val);
但是由于我們在每個 catch 分支里面都調用 exit() 退出程序,可以通過添加 finally 語句塊定義一個兜底邏輯:
$exit = false;
try {
$val = getItemFromBook($book, 'desc');
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
$exit = true;
} catch (OutOfBoundsException $exception) {
echo $exception->getMessage() . PHP_EOL;
$exit = true;
} finally {
$exit ? exit() : var_dump($val);
}
不管 try 語句塊中的代碼是否拋出異常,finally 語句塊中的代碼都會執行,如果拋出異常,則會先執行 catch 語句塊中的代碼,再執行 finally 語句塊中的代碼,否則會直接執行 finally 語句塊中的代碼。
拋出異常
我們也可以在捕獲到異常后不進行處理,直接拋出,交給上一層調用代碼進行進一步處理:
try {
$val = getItemFromBook([], null);
$val = getItemFromBook($book, 'desc');
} catch (InvalidArgumentException $exception) {
throw $exception;
} catch (OutOfBoundsException $exception) {
throw $exception;
} finally {
var_dump($val);
}
上一層的處理邏輯也無非是進行 try...catch... 捕獲后進行處理或者繼續拋出。
全局異常處理器
在進行系統框架設計時,考慮到系統的穩健型,總會有一些異常的「漏網之魚」沒有被捕獲和處理,這個時候就要通過 set_exception_handler 函數注冊全局的異常處理器來處理這些未被捕獲和處理的異常:
...
function myExceptionHandler(Exception $exception)
{
echo 'Uncaught Exception [' . get_class($exception) . ']: ' . $exception->getMessage() . PHP_EOL;
echo 'Thrown in ' . $exception->getFile() . ' on line ' . $exception->getLine() . PHP_EOL;
}
set_exception_handler('myExceptionHandler');
try {
$val = getItemFromBook($book, 'desc');
} catch (InvalidArgumentException $exception) {
throw $exception;
} catch (OutOfBoundsException $exception) {
throw $exception;
} finally {
if (isset($val)) {
var_dump($val);
} else {
echo '異常將通過全局異常處理器處理...' . PHP_EOL;
}
}
我們首先需要定義一個自定義的 myExceptionHandler 函數作為全局異常處理器,在這個函數中,我們需要傳入異常對象作為參數,然后輸出該異常類名、消息、出現異常的文件和行號,最后通過 set_exception_handler 函數將其注冊為全局異常處理器。
在后續調用 getItemFromBook 時,由于捕獲的異常拋給了上一層,但目前沒有上一層調用代碼,也就變成了未處理異常,最終這些異常會通過全局異常處理器進行兜底處理,執行上述代碼,輸出如下:
這里是將異常信息輸出到了標準輸出(STDOUT),如果是在線上生產環境,和自定義的全局錯誤處理器一樣,你也可以將這些信息記錄到日志文件中,或者發送到第三方日志處理服務。
自定義異常類
上面所有的異常都是 PHP 內置的異常類,除此之外,我們也可以根據需要創建自定義的異常類,只需要繼承自 Exception 基類或者其子類即可,比如我們為索引不存在定義一個獨立的異常類,并且繼承自 LogicException 父類:
class IndexNotExistsException extends LogicException
{
}
暫時不需要編寫任何方法,它可以繼承祖先類 Exception 的所有 protected/public 方法和屬性:
需要注意的是,Exception 類中的很多方法定義前面都有一個 final 關鍵字,通過該關鍵字修飾的方法不能被子類重寫,如果我們試圖這么做會報錯:
另外,final 還可以用于修飾類,通過 final 修飾的類將不能被子類繼承。
定義好自定義類之后,就可以在代碼中捕獲和處理了:
function getItemFromBook($book, $key)
{
...
if (!key_exists($key, $book)) {
throw new IndexNotExistsException("對應索引不存在!");
}
...
}
...
try {
$val = getItemFromBook($book, 'desc');
} catch (InvalidArgumentException $exception) {
throw $exception;
} catch (IndexNotExistsException $exception) {
throw $exception;
} finally {
if (isset($val)) {
var_dump($val);
} else {
echo '異常將通過全局異常處理器處理...' . PHP_EOL;
}
}
執行上述代碼,輸出結果如下:
說明自定義異常類已經可以正常使用。
在實際項目開發中,可以結合自定義異常類和上述異常處理方式構建自己的異常處理體系。
小結
關于 PHP 面向對象編程我們就簡單介紹到這里,通過前面的介紹,相信你已經對類和對象的實例化,類級別的靜態方法,類功能的垂直擴展(繼承、抽象類、接口)和水平擴展(對象組合、Trait)有了充分的認識,此外,PHP 類還支持特有的魔術方法,合理使用這些魔術方法可以進行一些很方便的初始化/善后清理工作,最后,對于程序中出現的錯誤和異常,可以通過一系列內置的機制進行捕獲和處理。
下篇教程,我們將開始介紹 PHP 中如何連接 MySQL 數據庫并進行增刪改查操作。
總結
以上是生活随笔為你收集整理的php面向对象异常处理,PHP 错误和异常处理(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java网络接口_java网络编程之识别
- 下一篇: 谈谈java面向对象思想_对于Java面