異常是所有開發者在寫代碼過程中都會遇到并且要妥善處理的,不少開發者認為php無需做異常處理,框架本身已經幫我們做好了,異常處理是JAVA,C語言才要做的事情,其實這種說法是很片面的,雖然PHP的框架比如Laravel,Yii等有非常好的異常處理,但是他們只是提供了一種工具,框架不會去理解業務,而在業務邏輯中,難免會遇到各種異常處理,我們要提升對異常的認知,做到在工作中靈活運用 ”
接下來在異常處理這篇文章中,我將從頭梳理PHP開發中 Exception 這個關鍵字的周邊知識,爭取梳理清楚如下的幾個問題
1.什么是異常
2.異常從哪里來
3.異常應該到哪里去
4.異常之分門別類
5.異常和錯誤的區別和聯系
6.異常之于業務場景
什么是異常
在我們開始所有解釋之前,我么首先舉個栗子. 假設你要按照給定id來查找humans表的用戶郵箱,這個功能可以寫成這樣:
但是這絕對不是一個健壯的代碼,一旦用戶傳遞一個不存在的id(數據庫不存在),或者傳遞是壓根就不是int型的正整數,程序就會進入: PHP Notice 狀態,導致整個程序進入非預期流程
這里的非預期流程可能是個PHP層的警告,也可能是個fatal error(假設方法的實現還有更加致命的邏輯假設),導致程序異常終止
所以這時候需要這個方法告訴調用方你的參數有誤這種信息,這個時候通常我們的做法有兩種.
1.
2.
而第二種做法就是我們討論的Exception,好了,到這里我們只是討論清楚了拋出異常的必要性,但是就如上面兩個例子看到的,調用方調用完畢這個方法怎么按照你給的信號(throw 出來的exception)做出合理的處理呢?
異常從哪里來?
上面說清楚了異常的必要性以及可能性,那么異常從哪里來呢?
異常來自開發者對整體代碼邏輯的非預期結果給出的提示. 所以簡單來說,異常是PHP代碼層拋出的. 一般常見的當我們調用第三方接口,如果不做異常處理,接口異常,或者超時,都會產生致命性錯誤。
異常應該到哪里去
上面已經定義了function getHumanEmailById,同時對參數的非法性做了異常的拋出,但是我們不知道異常到哪里去了,我們嘗試調用它getHumanEmailById(1),這里假設id是1的human信息在數據庫中已經被刪除,看看PHP執行器返回結果:
Fatal error: Uncaught InvalidArgumentException: 參數非法 這里發現,我們拋出的異常居然變成了Fatal error,所以異常拋出后交給了PHP解釋器,解釋器沒有找到 catch 這個異常的邏輯代碼,所以直接fatal error,說明這一點
我們繼續修改代碼
執行結果正常輸出: 參數非法 在Laravel中,內置了非常多的異常處理類,如果我們善于觀察,可能會經常遇到 諸如router 404, 405Method Not Allowed 429 to many request ,model 404 這樣的laravel 已經幫我們處理的異常處理,當然還是開頭那句話,我們業務的異常,框架是不會幫我們處理的 到此,我們解釋清楚了異常應該到哪里去的問題
異常之分門別類
也許你留意到上面的代碼沒有catch InvalidArgumentException,是因為Exception是InvalidArgumentException的父類,因為異常的類型很多,我們有一些需求確實需要根據不同的異常做不同的事情,例如下面的偽代碼:
異常和錯誤的區別和聯系
這個主題,我想只有PHP官方PHP5時代的開發才能解釋清楚這一點,通俗的來說,錯誤可能是不可修復的,當然現在的PHP7版本已經將error和exception統一了,他們都集成自throwable這個interface.具體的關系如圖
PHP7以下的話,我們可以嘗試將error轉為exception,具體實現代碼:
異常之于業務場景
特定一類throwable統一輸出json 最后,我們回歸到上面最初的代碼,如果human的id是非法的,就拋出了異常,假設這個id恰好是業務前端傳遞的,我們就需要告訴用戶這個id是非法的,明確告訴他非法請求. 實現的邏輯代碼大致為:
如果這中參數對應的msg想統一起來,且前端提交的參數非常多,都需要這樣判斷呢? 這里我們可以抽象出有一個類,繼承自InvalidArgumentException的類ApiInvalidArgumentException,然后統一在上層捕獲這個異常,然后統一輸出json格式 這里的最佳實踐可以在laravel中看到影子,大致思路如下:
1.繼承IlluminateFoundationExceptionsHandler::render($request, Exception $e)
2.ApiInvalidArgumentException類定義toJson方法
3.拿到$e,特判ApiInvalidArgumentException,return出tojson
如此對業務代碼無侵入,看起來干凈且明了
將沒有catch的異常介入第三方統計 線上在所難免的會有一些異常是未捕獲的,這時PHP會將信息直接fatal error,并輸出堆棧信息,所以我們可以將這些信息介入第三方,實時發消息給開發者,解決和發現線上問題. 推薦大家使用sentry作為PHP異常處理和發現的工具,很強大,目前我們團隊線上就在大量使用.
因為我們的業務需要判斷和處理太多不符合預期的結果了,有時候一個魯棒性強的代碼可能是核心業務代碼的2-3倍,這個時候如果我們能夠很好的利用exception,既能讓代碼健壯,又能讓健壯性判斷可讀性高,例如我們可以二次封裝if判斷,轉變為assert斷言,然后在斷言中拋出異常.現在很多第三方類庫,都很好的定義了自己的異常,為的就是健壯和可讀性,希望通過這篇文章,大家能夠收獲一些新的心得體會,也希望你能斧正文章的錯誤。