PHP錯誤異常處理詳解
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
異常處理(又稱為錯誤處理)功能提供了處理程式執行時出現的錯誤或異常情況的方法。
異常處理通常是防止未知錯誤產生所採取的處理措施。異常處理的好處是你不用再絞盡腦汁去考慮各種錯誤,這為處理某一類錯誤提供了一個很有效的方法,使程式設計效率大大提高。當異常被觸發時,通常會發生:
當前程式碼狀態被儲存
程式碼執行被切換到預定義的異常處理器函式
根據情況,處理器也許會從儲存的程式碼狀態重新開始執行程式碼,終止指令碼執行,或從程式碼中另外的位置繼續執行指令碼
PHP 5 提供了一種新的面向物件的錯誤處理方法。可以使用檢測(try)、丟擲(throw)和捕獲(catch)異常。即使用try檢測有沒有丟擲(throw)異常,若有異常丟擲(throw),使用catch捕獲異常。
一個 try 至少要有一個與之對應的 catch。定義多個 catch 可以捕獲不同的物件。PHP 會按這些 catch 被定義的順序執行,直到完成最後一個為止。而在這些 catch 內,又可以丟擲新的異常。
1. 異常的使用
當一個異常被丟擲時,其後的程式碼將不會繼續執行,PHP 會嘗試查詢匹配的 "catch" 程式碼塊。如果一個異常沒有被捕獲,而且又沒用使用set_exception_handler() 作相應的處理的話,那麼 PHP 將會產生一個嚴重的錯誤,並且輸出未能捕獲異常(
丟擲異常,但不去捕獲它:
<?phpini_set('display_errors', 'On');error_reporting(E_ALL & ~ E_WARNING);$error = 'Always throw this error';throw new Exception($error);// 繼續執行echo 'Hello World';?>
上面的程式碼會獲得類似這樣的一個致命錯誤:
Fatal error: Uncaught exception 'Exception' with message 'Always throw this error' in E:\sngrep\index.php on line 5Exception: Always throw this error in E:\sngrep\index.php on line 5Call Stack: 0.0005 330680 1. {main}() E:\sngrep\index.php:0
2. Try, throw 和 catch
要避免上面這個致命錯誤,可以使用try catch捕獲掉。
處理處理程式應當包括:
Try - 使用異常的函式應該位於 "try" 程式碼塊內。如果沒有觸發異常,則程式碼將照常繼續執行。但是如果異常被觸發,會丟擲一個異常。
Throw - 這裡規定如何觸發異常。每一個 "throw" 必須對應至少一個 "catch"
Catch - "catch" 程式碼塊會捕獲異常,並建立一個包含異常資訊的物件
丟擲異常並捕獲掉,可以繼續執行後面的程式碼:
<?phptry { $error = 'Always throw this error'; throw new Exception($error); // 從這裡開始,tra 程式碼塊內的程式碼將不會被執行 echo 'Never executed';} catch (Exception $e) { echo 'Caught exception: ', $e->getMessage(),'<br>';}// 繼續執行echo 'Hello World';?>
在 "try" 程式碼塊檢測有有沒有丟擲“throw”異常,這裡丟擲了異常。
"catch" 程式碼塊接收到該異常,並建立一個包含異常資訊的物件 ($e)。
通過從這個 exception 物件呼叫 $e->getMessage(),輸出來自該異常的錯誤訊息
為了遵循“每個 throw 必須對應一個 catch”的原則,可以設定一個頂層的異常處理器來處理漏掉的錯誤。
3. 擴充套件 PHP 內建的異常處理類
使用者可以用自定義的異常處理類來擴充套件 PHP 內建的異常處理類。以下的程式碼說明了在內建的異常處理類中,哪些屬性和方法在子類中是可訪問和可繼承的。(注:以下這段程式碼只為說明內建異常處理類的結構,它並不是一段有實際意義的可用程式碼。)
<?phpclass Exception{ protected $message = 'Unknown exception'; // 異常資訊 protected $code = 0; // 使用者自定義異常程式碼 protected $file; // 發生異常的檔名 protected $line; // 發生異常的程式碼行號 function __construct($message = null, $code = 0); final function getMessage(); // 返回異常資訊 final function getCode(); // 返回異常程式碼 final function getFile(); // 返回發生異常的檔名 final function getLine(); // 返回發生異常的程式碼行號 final function getTrace(); // backtrace() 陣列 final function getTraceAsString(); // 已格成化成字串的 getTrace() 資訊 /* 可過載的方法 */ function __toString(); // 可輸出的字串}
如果使用自定義的類來擴充套件內建異常處理類,並且要重新定義建構函式的話,建議同時呼叫parent::__construct() 來檢查所有的變數是否已被賦值。當物件要輸出字串的時候,可以過載__toString() 並自定義輸出的樣式。
構建自定義異常處理類:
<?php/** * * 自定義一個異常處理類 */class MyException extends Exception{ // 重定義構造器使 message 變為必須被指定的屬性 public function __construct($message, $code = 0) { // 自定義的程式碼 // 確保所有變數都被正確賦值 parent::__construct($message, $code); } // 自定義字串輸出的樣式 */ public function __toString() { return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; } public function customFunction() { echo "A Custom function for this type of exception\n"; }}// 例子 1:丟擲自定義異常,但沒有預設的異常echo ' 例子 1', '<br>';try { // 丟擲自定義異常 throw new MyException('1 is an invalid parameter', 5);} catch (MyException $e) { // 捕獲異常 echo "Caught my exception\n", $e; $e->customFunction();} catch (Exception $e) { // 被忽略 echo "Caught Default Exception\n", $e;}// 執行後續程式碼// 例子 2: 丟擲預設的異常 但沒有自定義異常echo '<br>', ' 例子 2:', '<br>';try { // 丟擲預設的異常 throw new Exception('2 isnt allowed as a parameter', 6);} catch (MyException $e) { // 不能匹配異常的種類,被忽略 echo "Caught my exception\n", $e; $e->customFunction();} catch (Exception $e) {// 捕獲異常 echo "Caught Default Exception\n", $e;}// 執行後續程式碼// 例子 3: 丟擲自定義異常 ,使用預設異常類物件來捕獲echo '<br>', ' 例子 3:', '<br>';try { // 丟擲自定義異常 throw new MyException('3 isnt allowed as a parameter', 6);} catch (Exception $e) { // 捕獲異常 echo "Default Exception caught\n", $e;}// 執行後續程式碼// 例子 4echo '<br>', ' 例子 4:', '<br>';try { echo 'No Exception ';} catch (Exception $e) { // 沒有異常,被忽略 echo "Default Exception caught\n", $e;}// 執行後續程式碼
MyException 類是作為舊的 exception 類的一個擴充套件來建立的。這樣它就繼承了舊類的所有屬性和方法,我們可以使用 exception 類的方法,比如 getLine() 、 getFile() 以及 getMessage()。
4. 巢狀異常處理
如果在內層 "try" 程式碼塊中異常沒有被捕獲,則它將在外層級上查詢 catch 程式碼塊去捕獲。
try { try { throw new MyException('foo!'); } catch (MyException $e) { /* 重新丟擲 rethrow it */ $e->customFunction(); throw $e; }} catch (Exception $e) { var_dump($e->getMessage());}
5. 設定頂層異常處理器 (Top Level Exception Handler)
set_exception_handler() 函式可設定處理所有未捕獲異常的使用者定義函式。
<?phpfunction myException($exception){echo "<b>Exception:</b> " , $exception->getMessage();}set_exception_handler('myException');throw new Exception('Uncaught Exception occurred');
輸出結果:
Exception: Uncaught Exception occurred
6. 異常的規則
- 需要進行異常處理的程式碼應該放入 try 程式碼塊內,以便捕獲潛在的異常。
- 每個 try 或 throw 程式碼塊必須至少擁有一個對應的 catch 程式碼塊。
- 使用多個 catch 程式碼塊可以捕獲不同種類的異常。
- 可以在 try 程式碼塊內的 catch 程式碼塊中再次丟擲(re-thrown)異常。
簡而言之:如果丟擲了異常,就必須捕獲它,否則程式終止執行。
在我們實際開發中,錯誤及異常捕捉僅僅靠try{}catch()是遠遠不夠的。
set_error_handler
一般用於捕捉 E_NOTICE 、E_USER_ERROR、E_USER_WARNING、E_USER_NOTICE
不能捕捉:
E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR and E_COMPILE_WARNING。
一般與trigger_error("...", E_USER_ERROR),配合使用。
PHP錯誤處理
在實際開發中,錯誤及異常捕捉僅僅靠try{}catch()是遠遠不夠的。
所以引用以下幾中函式。
a) set_error_handler
一般用於捕捉 E_NOTICE 、E_USER_ERROR、E_USER_WARNING、E_USER_NOTICE
不能捕捉:
E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR and E_COMPILE_WARNING。
一般與trigger_error("...", E_USER_ERROR),配合使用。
<?php// we will do our own error handlingerror_reporting(0);function userErrorHandler($errno, $errmsg, $filename, $linenum, $vars){ // timestamp for the error entry $dt = date("Y-m-d H:i:s (T)"); // define an assoc array of error string // in reality the only entries we should // consider are E_WARNING, E_NOTICE, E_USER_ERROR, // E_USER_WARNING and E_USER_NOTICE $errortype = array ( E_ERROR => 'Error', E_WARNING => 'Warning', E_PARSE => 'Parsing Error', E_NOTICE => 'Notice', E_CORE_ERROR => 'Core Error', E_CORE_WARNING => 'Core Warning', E_COMPILE_ERROR => 'Compile Error', E_COMPILE_WARNING => 'Compile Warning', E_USER_ERROR => 'User Error', E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice', E_STRICT => 'Runtime Notice', E_RECOVERABLE_ERROR => 'Catchable Fatal Error' ); // set of errors for which a var trace will be saved $user_errors = array(E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE); $err = "<errorentry>\n"; $err .= "\t<datetime>" . $dt . "</datetime>\n"; $err .= "\t<errornum>" . $errno . "</errornum>\n"; $err .= "\t<errortype>" . $errortype[$errno] . "</errortype>\n"; $err .= "\t<errormsg>" . $errmsg . "</errormsg>\n"; $err .= "\t<scriptname>" . $filename . "</scriptname>\n"; $err .= "\t<scriptlinenum>" . $linenum . "</scriptlinenum>\n"; if (in_array($errno, $user_errors)) { $err .= "\t<vartrace>" . wddx_serialize_value($vars, "Variables") . "</vartrace>\n"; } $err .= "</errorentry>\n\n"; echo $err;}function distance($vect1, $vect2) { if (!is_array($vect1) || !is_array($vect2)) { trigger_error("Incorrect parameters, arrays expected", E_USER_ERROR); return NULL; } if (count($vect1) != count($vect2)) { trigger_error("Vectors need to be of the same size", E_USER_ERROR); return NULL; } for ($i=0; $i<count($vect1); $i++) { $c1 = $vect1[$i]; $c2 = $vect2[$i]; $d = 0.0; if (!is_numeric($c1)) { trigger_error("Coordinate $i in vector 1 is not a number, using zero",E_USER_WARNING); $c1 = 0.0; } if (!is_numeric($c2)) { trigger_error("Coordinate $i in vector 2 is not a number, using zero",E_USER_WARNING); $c2 = 0.0; } $d += $c2*$c2 - $c1*$c1; } return sqrt($d);}$old_error_handle = set_error_handler("userErrorHandler");$t = I_AM_NOT_DEFINED; //generates a warning// define some "vectors"$a = array(2, 3, "foo");$b = array(5.5, 4.3, -1.6);$c = array(1, -3);//generate a user error$t1 = distance($c,$b);// generate another user error$t2 = distance($b, "i am not an array") . "\n";// generate a warning$t3 = distance($a, $b) . "\n";?>
b) set_exception_handler
設定預設的異常處理程式,用於沒有用 try/catch 塊來捕獲的異常。 在 exception_handler 呼叫後異常會中止。
與throw new Exception('Uncaught Exception occurred'),連用。
<?php// we will do our own error handlingerror_reporting(0);function exceptHandle($errno, $errmsg, $filename, $linenum, $vars){ // timestamp for the error entry $dt = date("Y-m-d H:i:s (T)"); // define an assoc array of error string // in reality the only entries we should // consider are E_WARNING, E_NOTICE, E_USER_ERROR, // E_USER_WARNING and E_USER_NOTICE $errortype = array ( E_ERROR => 'Error', E_WARNING => 'Warning', E_PARSE => 'Parsing Error', E_NOTICE => 'Notice', E_CORE_ERROR => 'Core Error', E_CORE_WARNING => 'Core Warning', E_COMPILE_ERROR => 'Compile Error', E_COMPILE_WARNING => 'Compile Warning', E_USER_ERROR => 'User Error', E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice', E_STRICT => 'Runtime Notice', E_RECOVERABLE_ERROR => 'Catchable Fatal Error' ); // set of errors for which a var trace will be saved $err = "<errorentry>\n"; $err .= "\t<datetime>" . $dt . "</datetime>\n"; $err .= "\t<errornum>" . $errno . "</errornum>\n"; $err .= "\t<errortype>" . $errortype[$errno] . "</errortype>\n"; $err .= "\t<errormsg>" . $errmsg . "</errormsg>\n"; $err .= "\t<scriptname>" . $filename . "</scriptname>\n"; $err .= "\t<scriptlinenum>" . $linenum . "</scriptlinenum>\n"; if (1) { $err .= "\t<vartrace>" . wddx_serialize_value($vars, "Variables") . "</vartrace>\n"; } $err .= "</errorentry>\n\n"; echo $err;}$old_except_handle = set_exception_handler("exceptHandle");//$t = I_AM_NOT_DEFINED; //generates a warning$a;throw new Exception('Uncaught Exception occurred'); ?>
c) register_shutdown_function
執行機制是:php把要呼叫的函式調入記憶體。當頁面所有PHP語句都執行完成時,再呼叫此函式。
一般與trigger_error("...", E_USER_ERROR),配合使用。
<?phperror_reporting(0);date_default_timezone_set('Asia/Shanghai');register_shutdown_function('my_exception_handler');$t = I_AM_NOT_DEFINED; //generates a warningtrigger_error("Vectors need to be of the same size", E_USER_ERROR); function my_exception_handler(){ if($e = error_get_last()) { //$e['type']對應php_error常量 $message = ''; $message .= "出錯資訊:\t".$e['message']."\n\n"; $message .= "出錯檔案:\t".$e['file']."\n\n"; $message .= "出錯行數:\t".$e['line']."\n\n"; $message .= "\t\t請工程師檢查出現程式".$e['file']."出現錯誤的原因\n"; $message .= "\t\t希望能您早點解決故障出現的原因<br/>"; echo $message; //sendemail to }}?>
2、簡要說明錯誤處理:
1)、使用指定的檔案記錄錯誤報告日誌
使用指定的檔案記錄錯誤報告日誌使用指定的檔案記錄錯誤報告日誌使用指定的檔案記錄錯誤報告日誌 如果使用自己指定的檔案記錄錯誤日誌,一定要確保將這個檔案存放在文件根目錄之外,以減少遭到攻擊的可能。並且該檔案一定要讓PHP指令碼的執行使用者(Web伺服器程序所有者)具有寫許可權。假設在Linux作業系統中,將/usr/local/目錄下的error.log檔案作為錯誤日誌檔案,並設定Web伺服器程序使用者具有寫的許可權。然後在PHP的配置檔案中,將error_log指令的值設定為這個錯誤日誌檔案的絕對路徑。
需要將php.ini中的配置指令做如下修改:
error_reporting = E_ALL ;將會向PHP報告發生的每個錯誤 display_errors = Off ;不顯示滿足上條 指令所定義規則的所有錯誤報告 log_errors = On ;決定日誌語句記錄的位置 log_errors_max_len = 1024 ;設定每個日誌項的最大長度 error_log = /usr/local/error.log ;指定產生的 錯誤報告寫入的日誌檔案位置
PHP的配置檔案按上面的方式設定完成以後,並重新啟動Web伺服器。這樣,在執行PHP的任何指令碼檔案時,所產生的所有錯誤報告都不會在瀏覽器中顯示,而會記錄在自己指定的錯誤日誌/usr/local/error.log中。此外,不僅可以記錄滿足error_reporting所定義規則的所有錯誤,而且還可以使用PHP中的error_log()函式,送出一個使用者自定義的錯誤資訊。
該函式的原型如下所示:
1. bool error_log ( string message [, int message_type [, string destination [, string extra_headers]]] )
此函式會送出錯誤資訊到Web伺服器的錯誤日誌檔案、某個TCP伺服器或到指定檔案中。該函式執行成功則返回TRUE,失敗則返回FALSE。第一個引數message 是必選項,即為要送出的錯誤資訊。如果僅使用這一個引數,會按配置檔案php.ini中所設定的位置處傳送訊息。第二個引數message_type為整數值:0表示送到作業系統的日誌中;1則使用PHP的Mail()函式,傳送資訊到某E-mail處,第四個引數extra_headers亦會用到;2則將錯誤資訊送到TCP 伺服器中,此時第三個引數destination表示目的地IP及Port;3則將資訊存到檔案destination中。
如果以登入Oracle資料庫出現問題的處理為例,該函式的使用如下所示:
<?php if(!Ora_Logon($username, $password)){ error_log("Oracle資料庫不可用!", 0); //將錯誤訊息寫入到作業系統日誌中 } if(!($foo=allocate_new_foo()){ error_log("出現大麻煩了!", 1, ". mydomain.com"); //傳送到管理員郵箱中 } error_log("搞砸了!", 2, "localhost:5000"); //傳送到本機對應5000埠的伺服器中 error_log("搞砸了!", 3, "/usr/local/errors.log"); //傳送到指定的檔案中 ?>
2)、 錯誤資訊記錄到作業系統的日誌裡
錯誤資訊記錄到作業系統的日誌裡錯誤資訊記錄到作業系統的日誌裡錯誤資訊記錄到作業系統的日誌裡 錯誤報告也可以被記錄到作業系統日誌裡,但不同的作業系統之間的日誌管理有點區別。在Linux上錯誤語句將送往syslog,而在Windows上錯誤將傳送到事件日誌裡。如果你不熟悉syslog,起碼要知道它是基於UNIX的日誌工具,它提供了一個API來記錄與系統和應用程式執行有關的訊息。Windows事件日誌實際上與UNIX的syslog相同,這些日誌通常可以通過事件檢視器來檢視。如果希望將錯誤報告寫到作業系統的日誌裡,可以在配置檔案中將error_log指令的值設定為syslog。
具體需要在php.ini中修改的配置指令如下所示:
error_reporting = E_ALL ;將會向PHP報告發生的每個錯誤 display_errors = Off ;不顯示 滿足上條指令所定義規則的所有錯誤報告 log_errors = On ;決定日誌語句記錄的位置 log_errors_max_len = 1024 ;設定每個日誌項的最大長度 error_log = syslog ;指定產生的錯誤報告寫入作業系統的日誌裡
除了一般的錯誤輸出之外,PHP還允許向系統syslog中傳送定製的訊息。雖然通過前面介紹的error_log()函式,也可以向syslog中傳送定製的訊息,但在PHP中為這個特性提供了需要一起使用的4個專用函式。
分別介紹如下:
define_syslog_variables()
在使用openlog()、syslog及closelog()三個函式之前必須先呼叫該函式。因為在呼叫該函式時,它會根據現在的系統環境為下面三個函式初使用化一些必需的常量。
openlog()
開啟一個和當前系統中日誌器的連線,為向系統插入日誌訊息做好準備。並將提供的第一個字串引數插入到每個日誌訊息中,該函式還需要指定兩個將在日誌上下文使用的引數,可以參考官方文件使用。
syslog()
該函式向系統日誌中傳送一個定製訊息。需要兩個必選引數,第一個引數通過指定一個常量定製訊息的優先順序。例如LOG_WARNING表示一般的警告,LOG_EMERG表示嚴重地可以預示著系統崩潰的問題,一些其他的表示嚴重程度的常量可以參考官方文件使用。第二個引數則是向系統日誌中傳送的定製訊息,需要提供一個訊息字串,也可以是PHP引擎在執行時提供的錯誤字串。
closelog()
該函式在向系統日誌中傳送完成定製訊息以後呼叫,關閉由openlog()函式開啟的日誌連線。
如果在配置檔案中,已經開啟向syslog傳送定製訊息的指令,就可以使用前面介紹的四個函式傳送一個警告訊息到系統日誌中,並通過系統中的syslog解析工具,檢視和分析由PHP程式傳送的定製訊息,如下所示:
<?php define_syslog_variables(); openlog("PHP5", LOG_PID , LOG_USER); syslog(LOG_WARNING, "警告報告向syslog中傳送的演示, 警告時間:".date("Y/m/d H:i:s")); closelog();
以Windows系統為例,通過右擊"我的電腦"選擇管理選項,然後到系統工具選單中,選擇事件檢視器,再找到應用程式選項,就可以看到我們自己定製的警告訊息了。上面這段程式碼將在系統的syslog檔案中,生成類似下面的一條資訊,是事件的一部分:
1. PHP5[3084], 警告報告向syslog中傳送的演示, 警告時間:2009/03/26 04:09:11.
使用指定的檔案還是使用syslog記錄錯誤日誌,取決於你所在的Web伺服器環境。如果你可以控制Web伺服器,使用syslog是最理想的,因為你能利用syslog的解析工具來檢視和分析日誌。但如果你的網站在共享伺服器的虛擬主機中執行,就只有使用單獨的文字檔案記錄錯誤日誌了。