之所以想着做錯誤和異常的自動處理是因為:
用的公司自己的框架寫API,沒有異常和錯誤相關功能,
而每次操作都進行try...catch,有點繁瑣不說,感覺還很雞肋,即使我catch到了,還是得寫代碼進行處理,哪怕封裝了一個方法進行處理也還是繁瑣,
這種情況應該是程序自動進行處理,不該是這樣弱智的人工try...catch,然后處理
以及其他同事寫代碼時並不能嚴格的進行try...catch
於是乎,想自己設計一個自動捕獲並處理錯誤和異常的功能。
發現了一篇深度好文:https://www.cnblogs.com/zyf-zhaoyafei/p/6928149.html
理解了PHP中自帶的 register_shutdown_function,set_error_handler,set_exception_handler這三個函數
通過這三個函數我們可以實現PHP假 自動捕獲異常和錯誤。PHP5也不支持ERROR的捕獲,只能try...catch (Exception)
三個函數必須理解,如果你想實現該功能,希望你可以先做個小demo體驗下三個函數的神奇之處
這三個函數需要你寫在框架的生命周期比較靠前的地方,如果不知道寫哪里,請寫在入口文件中,或者在入口文件require一下(下面的源碼可以直接require使用)
因為如果寫到其他文件中,而碰巧該文件有錯誤或者異常,那么這三個函數就不發生作用了,而入口文件我們都可以確保正確
正文:
首先介紹set_error_handler這個函數,這個函數用於捕獲低級別的錯誤,該函數只能捕獲系統產生的一些Warning、Notice級別的錯誤,並調用一個用戶自定義的錯誤處理函數。
比如在入口文件寫入 set_error_handler('low_level_error'),並且我們建立了low_level_error 自定義方法來進行日志的記錄 。
當發生Warning、Notice級別的錯誤時,系統會自動調用low_level_error方法,四個參數"type"、 "message"、"file" 和 "line"也是系統傳過來的,可以直接使用。
注意:使用此函數后 error_reporting ( E_ALL ) 無法提示warning和notice級別的警告, 如果本地開發,最好關閉此函數,在生產環境才使用; 使用 其它兩個函數 時錯誤提示信息不受影響
可以使用下面源碼中的單個方法進行測試, 只需echo $str; exit; 就好
第二個函數 register_shutdown_function, 這個函數與第一個函數搭配使用,可以捕捉到Fatal Error、Parse Error等高級別會讓程序中止的錯誤,
這個方法是PHP腳本執行結束前最后一個調用的函數,比如腳本錯誤、die()、exit、異常、正常結束都會調用。這個方法沒有接受參數,所以需要借助error_get_last() ,error_get_last函數可以獲取到
系統最近發生的一個錯誤,同樣包含 "type"、 "message"、"file" 和 "line"四個字段,需要注意的是 本函數捕捉到的 (2,8,32,128,512,1024,2048,8192)等錯誤都是警告級別的,自己酌情處理。
第三個函數set_exception_handler, 這個函數可以捕獲 沒有用try/catch塊來捕獲的異常,並且在回調函數調用后異常會中止(這個我沒有試,因為我捕捉異常后直接返回給前端錯誤code碼了)。
思路:通過set_error_handler 和 register_shutdown_function 兩個函數可以捕捉到所有的錯誤,通過set_exception_handler 捕捉所有未處理的異常,這樣程序中所有的錯誤和異常就被我們捕捉到了,
接下來,我們可以通過自定義函數對它們進行操作,由於我的業務是直接向前端輸出,因此只是記錄相關日志后直接echo並退出,大家可以在自定義函數內自己決定做什么處理。
還可以在兩個自定義的錯誤處理函數中再拋出異常,由自定義的異常處理類統一處理,這也是一個不錯的思路,大家可以嘗試下。
最后:
下面是相關的代碼,供大家參考和測試。
<?php /**引入本文件后,PHP會自動處理錯誤和異常, php版本5.6, php7+版本需要小調整,運行時根據提示調整即可 * 本方法直接捕捉錯誤並記錄日志,如有興趣,可以捕捉到錯誤后再次拋出異常,將錯誤轉為異常后,由set_exception_handler統一進行處理 * User: LiZheng 271648298@qq.com * Date: 2019/3/21 */ register_shutdown_function('high_level_error'); //使用了register_shutdown_function 后,當程序遇見一些致命錯誤時會自動調用函數 high_level_error set_error_handler('low_level_error'); //使用了set_error_handler 后,當程序遇見Notice 和Warning錯誤時會自動調用函數 low_level_error set_exception_handler('exception_log'); //使用了set_exception_handler后,當遇到所有的未捕獲的異常時會自動調用函數 exception_log /** * 該方法只能捕獲系統產生的一些Warning、Notice級別的錯誤 * @param $type * @param $message * @param $file * @param $line * User: LiZheng 271648298@qq.com * Date: 2019/3/21 */ function low_level_error($type, $message, $file, $line) { $time = date('Y-m-d H:i:s'); //拼接要記錄成日志的信息 $str = "\n ".$time.' set_error_handler: ' . exception_self::get_type($type) . ':' . $message . ' in ' . $file . ' on ' . $line . ' line .'; $str = addslashes($str); //記錄日志 error_log是PHP自帶函數,記錄到指定位置,詳情見官網 error_log($str,3,ROOT_DIR."/runtime/logs/php_error.log"); //ROOT_DIR為項目根目錄 //對於notice和warning 級別的錯誤,只進行記錄而不會終止程序(但在本地開發時希望提示,可以通過if語句判斷) //echo json_encode(array('code'=>“ERR_ERROR”, 'msg'=>$time, 'data'=>array()), true);exit; } /** * 捕捉一些致命錯誤 * User: LiZheng 271648298@qq.com * Date: 2019/3/21 */ function high_level_error() { //error_get_last() 獲取最近一條發生的錯誤,包含"type"、 "message"、"file" 和 "line" if ($error = error_get_last()) { $time = date('Y-m-d H:i:s'); //拼接要記錄成日志的信息 $str = "\n ".$time.' register_shutdown_function: Type:' . exception_self::get_type($error['type']) . ' Msg: ' . $error['message'] . ' in ' . $error['file'] . ' on line ' . $error['line']; $str = addslashes($str); //記錄日志 error_log是PHP自帶函數,記錄到指定位置,還可以發送郵件以及其他操作 error_log($str,3,ROOT_DIR."/runtime/logs/php_error.log"); //ROOT_DIR為項目根目錄 //直接輸出接口的返回信息, 給前端一個錯誤碼code, msg返回時間用於方便查找日志 //echo json_encode(array('code'=>“ERR_ERROR”, 'msg'=>$time, 'data'=>array()), true);exit; if(in_array($error['type'], array(2,8,32,128,512,1024,2048,8192)))//本條件中的錯誤均為非致命錯誤, 8192 錯誤是棄用提示 { // if(APP_ENV == 'LOCAL') //這里可以對本地開發環境時進行不同的處理 // { // header('Content-Type: text/html; charset=utf-8'); // echo $str."\n"; // } }else { // if(APP_ENV == 'LOCAL') // { // header('Content-Type: text/html; charset=utf-8'); // echo $str."\n"; // } //直接輸出接口的返回信息, 給前端一個錯誤碼code, msg返回時間用於方便查找日志 echo json_encode(array('code'=>ERR_ERROR, 'msg'=>$time, 'data'=>array()), true);exit; } } } /** * 捕捉異常 * @param $exception * User: LiZheng 271648298@qq.com * Date: 2019/3/21 */ function exception_log($exception) { $time = date('Y-m-d H:i:s'); //拼接要記錄成日志的信息 $str = "\n ".$time." set_exception_handler: Exception: ". $exception->getMessage()." in ".$exception->getFile()." on line ". $exception->getLine(); $str = addslashes($str); //記錄日志 error_log($str,3,ROOT_DIR."/runtime/logs/php_error.log"); //輸出信息 echo json_encode(array('code'=>“ERR_EXCEPTION”, 'msg'=>$time, 'data'=>array()), true);exit; } /** * Class error_handler * Create on 2019/3/21 14:15 * User: LiZheng 271648298@qq.com * Date: 2019/3/21 */ class exception_self extends Exception { /** * 構造函數 * @param string $message * @param string $code */ public function __construct($message=null, $code=null) { parent::__construct($message, $code); // self::log($this->__toString()); } //PHP的錯誤級別 public static $type = array( '1' => 'E_ERROR ', '2' => 'E_WARNING ', '4' => 'E_PARSE ', '8' => 'E_NOTICE ', '16' => 'E_CORE_ERROR ', '32' => 'E_CORE_WARNING ', '64' => 'E_COMPILE_ERROR ', '128' => 'E_COMPILE_WARNING ', '256' => 'E_USER_ERROR ', '512' => 'E_USER_WARNING ', '1024' => 'E_USER_NOTICE ', '2048' => 'E_STRICT ', '4096' => 'E_RECOVERABLE_ERROR ', '8191' => 'E_ALL ', ); /** * 通過數字 置換 對應的英文 * @param $key * @return mixed * User: LiZheng 271648298@qq.com * Date: 2019/3/21 */ public static function get_type($key) { if(isset(self::$type[$key])) { return self::$type[$key]; }else { return $key; } } }