開發中使用的框架,大均可以作到優雅的回顯出語法級的錯誤,即 Parse Error(syntax error)E_PARSE,此錯誤做爲面向用戶代碼最底層的錯誤如何進行捕獲?php
下面主要講一下如何捕獲 E_PARSE & E_ERROR 錯誤,這裏我刻意的把 E_PARSE 錯誤放前位的,由於 E_PARSE 是面向用戶腳本第一位的錯誤,即如有必然最早發生。然後纔是 E_ERROR & E_WARNING & E_NOTICE ....一類的運行時錯誤。laravel
PHP 錯誤級別瀏覽器
# 系統級用戶代碼的一些錯誤類型 可由 try ... catch ... 捕獲 E_PARSE 解析時錯誤 語法解析錯誤 少個分號 多個逗號一類的 致命錯誤 E_ERROR 運行時錯誤 好比調用了未定義的函數或方法 致命錯誤 # 可由 set_error_handler 捕獲處理 E_WARNING 運行時警告 調用了未定義的變量 E_NOTICE 運行時提醒 E_DEPRECATED 運行時已廢棄的函數或方法 # Zend Engine 相關的一些錯誤 內存錯誤一類的 應該也能經過 try ... catch ... 捕獲 略難測試 E_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_COMPILE_WARNING # 用戶級自定義錯誤 可由 trigger_error 觸發 可由 set_error_handler 捕獲處理 E_USER_ERROR 用戶自定義錯誤 致命錯誤 未處理也會致使程序退出 E_USER_WARNING E_USER_NOTICE E_USER_DEPRECATED #編碼標準化警告(建議如何修改以向前兼容) E_STRICT 部分 捕獲的話 try ... catch ... 部分 set_error_handler E_RECOVERABLE_ERROR
先看一些問題代碼服務器
一、想關閉全部的錯誤報告框架
<?php // 不報告任何級別的錯誤 error_reporting(0); // 關閉錯誤回顯 ini_set('display_errors', false); echo 'i lost semicolon operator'
PHP 依然使用自身的錯誤機制報錯,緣由很簡單:語法解析 -- 解釋運行 -- 結束退出。當腳本最基本的語法存在問題時,Zend Engine 自身就會退出執行,並回顯 Parse ERROR 錯誤信息。此時還未解釋執行用戶代碼,即 error_reporting(0) 尚未在 Zend Engine 中對運行時作運行時環境的設定。函數
二、想使用 set_error_handler 捕捉錯誤oop
<?php // 報告全部級別的錯誤 error_reporting(E_ALL); // 自定義錯誤捕捉器 set_error_handler(function ($error_no, $error_str, $error_file, $error_line) { }, E_ALL | E_STRICT); echo 'i lost semicolon operator'
依然得不到理想的結果。測試
首先,這段代碼也是在解析階段就報錯了,Parse Error 直接退出了,尚未真的執行 set_error_handler()。ui
官方原話講解:this
若是錯誤發生在腳本執行以前(好比文件上傳時),將不會調用自定義的錯誤處理程序由於它還沒有在那時註冊。
再說,退一步講, set_error_handler 是用來自定義用戶級錯誤 E_USER_ERROR & E_USER_WARNING & E_USER_NOTICE & E_USER_DEPRECATED 和 部分運行時系統錯誤 E_WARING & E_NOTICE & E_DEPRECATED 的捕獲器,即語法解析錯誤 E_PARSE (Parse Error) 是沒法用其捕獲到的。
官方原話講解:
如下級別的錯誤不能由用戶定義的函數來處理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在調用 set_error_handler() 函數所在文件中產生的大多數 E_STRICT。
若是定義的 set_error_handler 的 handler 最後返回了 false,則此錯誤信息會繼續被 PHP 的標準錯誤處理程序處理:經過 error_reporting 的級別設定,該回顯的回顯(display_errors),該寫入錯誤日誌的寫入錯誤日誌(log_errors & error_log)
官方原話講解:
重要的是要記住 error_types 裏指定的錯誤類型都會繞過 PHP 標準錯誤處理程序, 除非回調函數返回了 FALSE。
注意,set_error_handler 是有本身的捕獲級別的,默認 E_ALL | E_STRICT,不過要出去上文說的那幾個級別,且不受 error_reporting() 設定的級別影響,即便你 error_reporting(0),set_error_handler 依然能捕捉到相應的錯誤。
一、爲什麼不少框架均可以優雅的捕獲到語法或致命錯誤(E_PARSE & E_ERROR)呢?好比 laravel 標配的 whoops 二、E_PARSE & E_ERROR 到底如何才能捕捉到? 三、以上示例貌似都在說 E_PARSE & E_ERROR 這種錯誤沒法捕獲,那讓用戶來自定義告警的 error_reporting() 的級別裏爲什麼還有它倆?
既然有相應的級別設置,那就說明是能夠被捕捉的,先簡單說明一下,E_ERROR 的捕捉其實很簡單,E_PARSE 的捕捉則需解釋和理解一下,會涉及到 PHP 解析和運行腳本的機制流程。
其實很是簡單,看一遍就理解了(下文中一些運行機制用詞可能不許確,還請大佬放過,一切爲了讓你們能容易理解)。
一、php 在解釋運行用戶代碼時,會以主腳本爲載入點,Zend Engine 首先對其進行語法解析(Parse),這裏必定要理解,Zend Engine 此時是對腳本的語法進行解析,腳本中的任何 ini 設置都對其無效(還沒解釋載入執行初始化),因此你設置的什麼 error_reporting, display_errors, set_error_handler。只有當語法解析無誤,Zend Engine 開始載入並解釋腳本,腳本里的一些參數設置項纔會開始生效。
二、php 沒有 //連接依賴庫 -- 編譯 -- 運行// 一說。當 php 在主腳本中 「引入依賴」 時,Zend Engine 並不會在對主腳本作語法解析時將其 「依賴」 也載入解析。Zend Engine 只會對當前的主腳本作語法解析,在解析經過後,便開始解釋執行用戶代碼,即使 「依賴」 中有 Parse Error,那也得等到真的執行到載入命令時纔會加載解析-解釋-運行。
因此,咱們首先要構建一個 Parse OK 的容器,初始化 Zend Engine 的一些運行時配置,好比關閉錯誤報告,這樣整個運行時就是關閉了錯誤報告的上下文,即使後續有 E_PAESE & E_ERROR 也不會回顯錯誤信息了。但咱們的目的是要捕捉。
<?php error_reporting(E_ALL); // main script echo "this is main script" . PHP_EOL; // 只有當 main script 語法解析 ok,開始載入解釋執行到此處時 // lib.php 纔會開始被 Zend Engine 作語法解析/解釋運行 require_once __DIR__ . '/lib.php'; echo "hello world!" . PHP_EOL;
解析過程:
1:error_reporting(E_ALL); 語法無誤 繼續 2:echo "this is main script" . PHP_EOL; 語法無誤 繼續 3:require_once __DIR__ . '/lib.php'; 此語法無誤 繼續 (注意:此時並不會去載入並對 lib.php 作語法解析檢查) 4:echo "hello world!" . PHP_EOL; 語法無誤繼續
解析完成,語法經過,開始解釋執行
執行過程:
1:error_reporting(E_ALL); 將執行環境的錯誤告警設爲用戶定義的級別,運行時用戶上下文已開始造成 2:echo "this is main script" . PHP_EOL; 輸出個字符串 3:require_once __DIR__ . '/lib.php'; 加載不曾載入過的腳本?開始加載執行 解析 - 解釋 的流程 4:echo "hello world!" . PHP_EOL; 要在 lib.php 被 解析 - 解釋 完成後纔會回到此處繼續執行
是否是發現了?在 lib.php 被載入前,main script 的一些運行時的參數設置已經生效,好比這裏的 error_reporting(E_ALL),lib.php 解析/解釋運行時已是在咱們自定義好錯誤告警級別的上下文中了,Zend Engine 會根據咱們設定的錯誤告警級別對 lib.php 進行載入。這時就能夠明白 E_PARSE & E_ERROR 錯誤可被用戶設定的含義了吧。
即:你首先要有一個絕對正確的容器,負責將一些必要的用戶設定傳遞給 Zend Engine 初始化好運行時上下文,此後再載入執行的用戶代碼都將在此上下文中執行,其後的業務邏輯。
示例:
一、關閉全部的錯誤報告
main.js
<?php // main.js as a ini init container error_reporting(0); echo "this is main script" . PHP_EOL; require_once __DIR__ . "/lib.php"; echo "hello world!" . PHP_EOL;
lib.js
<?php // lib.js as logic echo 'i lost semicolon operator'
那麼 lib.php 的任何錯誤都不會被報告出來,由於 main 運行到載入 lib 時,其已向 Zend Engine 發送了 error_reporting(0); 的指令,因此 lib 中的 Parse Error 不會被報告出來。但這並非咱們想要的,咱們要捕獲纔對。
二、捕獲系統級錯誤 E_PARSE & E_ERROR
首先咱們要有一個容器,讓 Zend Engine 載入並初始化運行時候,開始執行。
而後咱們可使用 try ... catch 捕捉錯誤,以下:
<?php // main.js as a ini init container error_reporting(E_ALL); try { require_once __DIR__ . '/lib.php'; } catch (\Exception $exception) { var_export($exception); } catch (\Error $error) { // 就是這裏了,try catch 捕捉了 Error var_export($error); }
輸出結果:
ParseError::__set_state(array( 'message' => 'syntax error, unexpected end of file, expecting \',\' or \';\'', 'string' => '', 'code' => 0, 'file' => '...\lib.php', 'line' => 2, 'trace' => array (), 'previous' => NULL, ))
這樣便優雅的拿到了 Parse Error 錯誤,包裝一下輸出給用戶便可。
Parse Error 能夠說是用戶級的最高一級錯誤了,Parse Error 了用戶腳本就退出了。
然後咱們纔會可能遇到 E_ERROR & E_WARNING & E_NOTICE & E_DEPRECATED 等,以下:
<?php error_reporting(E_ALL); try { // 調用一個不存在的方法 func_not_exists("hello world"); } catch (\Exception $exception) { var_export($exception); } catch (\Error $error) { // 就是這裏了,try catch 捕捉了 Error var_export($error); }
運行結果
Error::__set_state(array( 'message' => 'Call to undefined function func_not_exists()', 'string' => '', 'code' => 0, 'file' => '...main.php', 'line' => 47, 'trace' => array(), 'previous' => null, ))
如上,語法沒有問題,因此不會有 Parse Error,Zend Engine 開始載入腳本解釋執行,由於調用了不存在的方法,E_ERROR 觸發後被咱們捕獲。
try ... catch 能夠捕捉 E_PARSE & E_ERROR
set_error_handler 能夠捕捉 E_WARNING & E_NOTICE & E_DEPRECATED & E_USER_*
兩者聯合起來便可捕捉大部分的用戶代碼層面的錯誤
<?php // 設定錯誤監聽的級別 // 但不會影響 set_error_handler 和 try ... catch 的捕獲 // set_error_handler 和 try ... catch 是將錯誤處理交給用戶 // 只有當用戶沒有對錯誤作處理時 // 錯誤纔會根據 error_reporting / display_errors / log_errors / error_log 進行 php 標準的錯誤處理流程 error_reporting(E_ALL); // 是否回顯錯誤信息,默認 true // 則會將全部監聽到的錯誤信息回顯到標準輸出:瀏覽器或者命令行 // 線上環境強烈建議關閉 錯誤信息會暴露服務器相關信息 ini_set('display_errors', false); // 開啓錯誤日誌 // 線上環境強烈建議開啓 記錄錯誤日誌 ini_set('log_errors', true); // 錯誤日誌的位置 注意:若是 error_log 的路徑有誤的話 display_errors 會被強制打開 回顯錯誤到標準輸出 ini_set('error_log', __DIR__ . '/error.log'); // 以上爲 php 標準錯誤處理 的設定 // E_WARNING E_NOTICE E_DEPRECATED E_USER_* E_STRICT 捕獲 set_error_handler(function ($error_no, $error_str, $error_file, $error_line) { echo "erro_no: " . $error_no . " error_str: " . $error_str . PHP_EOL; //注意 程序並不會在這裏退出執行 //注意 若是返回了 false 錯誤會被 php 標準錯誤處理流程處理 }, E_ALL | E_STRICT); // E_PARSE & E_ERROR 捕捉 try { // E_WARNING 被 set_error_handler 捕獲 echo $variable_not_exists; // E_ERROR 被 try ... catch 捕獲 func_not_exists("function not exists!"); // E_PARSE 被 try ... catch 捕獲 lib.php 中有語法錯誤 require_once __DIR__ . '/lib.php'; } catch (\Exception $exception) { echo var_export($exception, true) . PHP_EOL; } catch (\Error $error) { echo var_export($error, true) . PHP_EOL; } echo "run finished" . PHP_EOL;
注意 set_error_handler 和 try ... catch 對錯誤捕獲後程序會繼續執行下去,並不會當即退出。
E_ERROR & E_PARSE 使用 try ... catch 捕獲
E_WARNING & E_NOTICE & E_DEPRECATED & E_USER_* 使用 set_error_handler 捕捉
若沒有作相應的處理,則錯誤信息會提交至 PHP 標準錯誤處理流程,根據 error_reporting / display_errors / log_errors / error_log 的設定進行處理。