本篇文章介紹的內容是PHP程式員遇到的錯誤與異常中的異常問題,現在分享給大家,有需要的朋友可以參考一下
上一篇:那些年,PHPer遇到的錯誤與異常:上篇之錯誤
一、PHP中的異常簡介及使用
1.1 異常執行流程
try{ // 需要進行異常處理的程式碼片段; throw 語句拋出異常;}catch( Exception $e ){ ... }catch( Exception $e ){ // 處理異常}contine.....
未被捕獲的異常會報致命錯誤:Fatal error:Uncaught exception.....
1.2 PHP異常特點
PHP
不會主動捕獲異常,需要程式中主動拋出 (throw
)異常,才能捕獲。
throw
會自動向上拋出
throw
之後的語句不會執行
try
後必須有catch
,否則解析錯誤Parse error
try{ $num1=3; $num2=0; if($num2==0){ throw new Exception('0不能當作除數'); echo 'this is a test';//看不到 }else{ $res=$num1/$num2; }}catch(Exception $e){ echo $e->getMessage();}
1.3 PHP內建異常
Php
不像java
提供了很多異常類,所以很多異常都會當成錯誤。要想變錯誤為拋出異常,需要手動throw
異常對象
PHP內建異常如:PDOException
、SplFileObject
可以自動拋出異常,後面的代碼可以繼續執行。
1.4 錯誤和異常的區別
1.4.1 異常處理
當異常被拋出,throw
後的代碼不會繼續執行,PHP
會嘗試尋找匹配的 catch
代碼塊。如果異常沒有被捕獲,而且又沒用使用set_exception_handler()
作相應的處理的話,那麼將發生一個嚴重的錯誤(致命錯誤),並且輸出 “Uncaught Exception
” (未捕獲異常)的錯誤訊息。
1.4.2 異常的基本文法結構
try
- 需要進行異常處理的代碼應該放入try
代碼塊內,以便捕獲潛在的異常。如果沒有觸發異常,則代碼將照常繼續執行。但是如果異常被觸發,會拋出一個異常
throw
- 這裡規定如何觸發異常。每一個try或 throw 必須
對應至少一個 catch。使用多個catch代碼塊可以捕獲不同種類的異常。
catch
- catch代碼塊會捕獲異常,並建立一個包含異常資訊的對象
1.4.3 重新拋出異常
有時,當異常被拋出時,也許希望以不同於標準的方式對它進行處理。可以在一個 catch 代碼塊中再次拋出異常。注意再次拋出異常需要try{}catch{},不能直接在catch代碼塊中throw異常
。
指令碼應該對使用者隱藏系統錯誤。對程式員來說,系統錯誤也許很重要,但是使用者對它們並不感興趣。為了讓使用者更容易使用,您可以再次拋出帶有對使用者比較友好的訊息的異常。
簡而言之:如果拋出了異常,就必須捕獲它。
1.4.4 錯誤與異常的區別
異常:程式運行與預期不太一致
錯誤:觸發的是本身的錯誤
當遇到錯誤的時候,觸發的是本身的錯誤,不會自動的拋出異常。異常可以通過throw
語句拋出異常,通過catch
捕獲異常,如果未捕獲會產生致命錯誤。
錯誤在發生的時候或觸發的時候,必須馬上對指令碼進行處理。異常可以一一向上傳遞,直到被捕獲,再處理。
錯誤觸發不具有相關代碼或名稱。異常可以自訂處理錯誤資訊(異常的好處就體現出來了),是通過代碼來拋出,捕獲然後處理
二、自訂異常類
2.1 自訂異常類
自訂異常類只能重寫建構函式和toString
兩個函數
自訂異常類可以增加自己的方法
多個catch
時,一般Exception
基類放在最後,基類可以調用自訂異常類定義的方法
/** * 自訂異常類 * Class MyException */class MyException extends Exception{ public function __construct($message = "", $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); } public function __toString() { $message = "<h2>出現異常了,資訊如下</h2>"; $message .= "<p>".__CLASS__."[{$this->code}]:{$this->message}</p>"; return $message; } public function test() { echo 'this is a test'; } public function stop() { exit('script end...'); } //自訂其它方法}try{ echo '出現異常啦'; throw new MyException('測試自訂異常');}catch (MyException $exception){ echo $exception->getMessage(); echo $exception;}//會繼續執行echo 'continue.........';
try{ throw new MyException('測試自訂異常');}catch (Exception $exception){ echo $exception->getMessage(); $exception->test();} catch (MyException $exception){ echo $exception->getMessage();}
2.2 小技巧
//將錯誤用錯誤抑制符吸收,然後拋出異常If(@!fwrite($filename,$data)) throw new exception(自訂異常)PHP_EOL #分行符號
記錄錯誤記錄檔資訊方式:
(1) :file_put_contents(LOG_PATH.'error.log';, '錯誤資訊'.' '.date('Y-m-d H:i:s')."\r\n", FILE_APPEND);
(2) :error_log('錯誤資訊'.' '.date('Y-m-d H:i:s')."\r\n",3,LOG_PATH.'error.log');
2.3 使用觀察者模式處理異常資訊
Exception_Observer.php/** * 給觀察者定義規範 * * Interface Exception_Observer */interface Exception_Observer{ public function update(Observable_Exception $e);}
Observable_Exception.php/** * 定義觀察者 * Class Observable_Exception */class Observable_Exception extends Exception{ //儲存觀察者資訊 public static $_observers = array(); public static function attach(Exception_Observer $observer) { self::$_observers[] = $observer; } public function __construct($message = "", $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->notify(); } public function notify() { foreach (self::$_observers as $observer) { $observer->update($this); } }}
Logging_Exception_Observer.php/** * 記錄錯誤記錄檔 * Class Logging_Exception_Observer */class Logging_Exception_Observer implements Exception_Observer{ protected $_filename = __DIR__.'/error_observer.log'; public function __construct($filename = null) { if ($filename!==null && is_string($filename)){ $this->_filename = $filename; } } public function update(Observable_Exception $e) { $message = "時間:".date('Y:m:d H:i:s',time()).PHP_EOL; $message.= "資訊:".$e->getMessage().PHP_EOL; $message.= "追蹤資訊:".$e->getTraceAsString().PHP_EOL; $message.= "檔案:".$e->getFile().PHP_EOL; $message.= "行號:".$e->getLine().PHP_EOL; error_log($message,3,$this->_filename);//寫到日誌中 }}
test.php/** *測試 */header('content-type:text/html;charset=utf-8');require_once 'Exception_Observer.php';require_once 'Logging_Exception_Observer.php';require_once 'Observable_Exception.php';Observable_Exception::attach(new Logging_Exception_Observer());class MyException extends Observable_Exception{ public function test() { echo 'this is a test'; }}try{ throw new MyException('出現了異常!');}catch (MyException $exception){ echo $exception->getMessage();}
三、自訂異常處理器
3.1 如何自訂異常處理器
3.1.1 自訂異常處理器
類似set_error_handler
接管系統的錯誤處理函數,set_exception_handler
接管所有沒有被catch
的異常
restore_exception_handler
同restore_error_handler
一樣,本質上應該說從異常/錯誤處理函數棧中彈出一個。比如有一個異常處理函數,彈出一個的話,就沒有異常處理函數,如果有異常沒有捕獲,會交由錯誤處理函數,如沒有錯誤處理函數,異常最終會有系統錯誤處理函數處理。如果設定了2個異常處理函數,彈出一個,會交由下面一個異常處理函數處理。
/** * 自訂異常函數處理器 */header('content-type:text/html;charset=utf-8');function exceptionHandler_1($e){ echo '自訂異常處理器1<br/>函數名:'.__FUNCTION__.PHP_EOL; echo '異常資訊:'.$e->getMessage();}function exceptionHandler_2($e){ echo '自訂異常處理器2<br/>函數名:'.__FUNCTION__.PHP_EOL; echo '異常資訊:'.$e->getMessage();}set_exception_handler('exceptionHandler_1');//set_exception_handler('exceptionHandler_2');//恢複到上一次定義過的異常處理函數,即exceptionHandler_1//restore_exception_handler();//致命錯誤資訊//restore_exception_handler();throw new Exception('測試自訂異常處理器');//自訂異常處理器,不會向下繼續執行,因為throw之後不會再繼續執行;try{} catch{}之後,會繼續執行//回顧:自訂錯誤處理器會繼續執行代碼,而手動拋出的錯誤資訊不會繼續執行echo 'test';
/** * 自訂異常類處理器 * Class ExceptionHandler */class ExceptionHandler{ protected $_exception; protected $_logFile = __DIR__.'/exception_handle.log'; public function __construct(Exception $e) { $this->_exception = $e; } public static function handle(Exception $e) { $self = new self($e); $self->log(); echo $self; } public function log() { error_log($this->_exception->getMessage().PHP_EOL,3,$this->_logFile); } /** * 魔術方法__toString() * 快速擷取對象的字串資訊的便捷方式,直接輸出對象引用時自動調用的方法。 * @return string */ public function __toString() { $message = <<<EOF <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>出現異常了啊啊啊啊</h1> </body> </html>EOF; return $message; }}set_exception_handler(array('ExceptionHandler','handle'));/** * try catch不會被自訂異常處理!!!! */try{ throw new Exception('this is a test');}catch (Exception $exception) { echo $exception->getMessage();}throw new Exception('測試自訂的異常處理器');
3.1.2 錯誤/異常之後是否繼續執行代碼問題總結
異常:
自訂異常處理器不會向下繼續執行,因為
throw
之後不會再繼續執行
try{} catch{}
之後,會繼續執行
錯誤:
自訂錯誤處理器會繼續執行代碼,而手動拋出的錯誤資訊不會繼續執行
3.2 像處理異常一樣處理PHP錯誤
3.2.1 方式一:ErrorException
/** * 方式一:ErrorException錯誤異常類 * @param $errno * @param $errstr * @param $errfile * @param $errline * @throws ErrorException */function exception_error_handler($errno,$errstr,$errfile,$errline){ throw new ErrorException($errstr,0,$errno,$errfile,$errline);}set_error_handler('exception_error_handler');try{ echo gettype();}catch (Exception $exception){ echo $exception->getMessage();}
3.2.2 方式二:自訂異常類,繼承基類Exception
/** * 方式二:自訂異常類 * Class ErrorToException *///顯示所有的錯誤error_reporting(-1);class ErrorToException extends Exception{ public static function handle($errno,$errstr) { throw new self($errstr,0); }}set_error_handler(array('ErrorToException','handle'));set_error_handler(array('ErrorToException','handle'),E_USER_WARNING|E_WARNING);try{ echo $test;//notice,不會被處理 echo gettype();//warning //手動觸發錯誤 trigger_error('test',E_USER_WARNING);}catch (Exception $exception){ echo $exception->getMessage();}
3.3 PHP頁面重新導向實現
header('Content-type:text/html;charset=utf-8');class ExceptionRedirectHandler{ protected $_exception; protected $_logFile = __DIR__.'redirect.log'; public $redirect='404.html'; public function __construct(Exception $e){ $this->_exception=$e; } public static function handle(Exception $e){ $self=new self($e); $self->log(); // ob_end_clean()清除所有的輸出緩衝,最後沒有緩衝的時候會產生通知層級的錯誤 while(@ob_end_clean()); header('HTTP/1.1 307 Temporary Redirect'); //臨時重新導向 header('Cache-Control:no-cache,must-revalidate');//no-cache強制向原始伺服器再次驗證,must-revalidate可緩衝但必須再向原始伺服器進行確認 header('Expires: Sat, 28 Mar 2016 13:28:48 GMT'); //資源失效的時間 header('Location:'.$self->redirect); //跳轉 } public function log(){ error_log($this->_exception->getMessage().PHP_EOL,3,$this->_logFile); }}set_exception_handler(array('ExceptionRedirectHandler','handle'));$link=@mysqli_connect('127.0.0.1','root','1234561');if(!$link){ throw new Exception('資料庫連接出錯啦');}
完!
參考課程視頻:那些年你遇到的錯誤與異常
相關推薦:
PHP程式員遇到的錯誤與異常上篇之錯誤