Chinaunix首页 | 论坛 | 博客
  • 博客访问: 322732
  • 博文数量: 96
  • 博客积分: 230
  • 博客等级: 二等列兵
  • 技术积分: 722
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-13 22:25
个人简介

心安处即吾乡!

文章分类

全部博文(96)

文章存档

2016年(1)

2014年(79)

2013年(7)

2012年(9)

我的朋友

分类: PHP

2014-07-02 17:23:33

在開發 PHP 的時候,最麻煩的事情之一就是處理錯誤。一個好的程式除了要將錯誤訊息呈現給使用者知道之外,也要讓該結束的部份正常結束才行。

而在 PHP5 之後,除了以往的 Error Handling 之外,還多了 Exception Handling ,使得程式變得更難去處理錯誤;所以大多數的開發者只能雙手一攤,讓這些錯誤訊息大剌剌地出現在使用者面前。

有沒有什麼好方法可以讓我們好好控制 Error 和 Exception 呢?

傳統的做法


在很多書籍和網路範例裡,當程式出錯時就是讓程序直接死掉,最常見的就是資料庫連線:

点击(此处)折叠或打开

  1. $link = mysql_connect('localhost', 'mysql_user', 'mysql_password');
  2. if (!$link) {
  3.     die('Could not connect: ' . mysql_error());
  4. }
  5. echo 'Connected successfully';
  6. mysql_close($link);

或是在送出導向 header 後,就直接 exit :

点击(此处)折叠或打开

  1. header("Location: "); /* Redirect browser */
  2. /* Make sure that code below does not get executed when we redirect. */
  3. exit;

這些都不是好做法,因為有些流量較大的網站裡可能有多個資料庫連線,或是檔案的 handler 仍在開啟中;如果直接讓程序死亡或離開的話,就沒辦法將這些已經開啟的資源給正常關閉掉,進而造成系統的不穩定。

Exception 的處理


PHP5 中,有個 set_exception_handler 這個函式,它可以幫我們處理 Exception :

点击(此处)折叠或打开

  1. function exception_handler($exception)
  2. {
  3.   echo "Uncaught exception: " , $exception->getMessage(), "\n";
  4. }
  5. set_exception_handler('exception_handler');
  6. throw new Exception('Uncaught Exception');
  7. echo "Not Executed\n";

不過我個人認為用 try…catch 會讓我們在程式流程上的彈性更大:

点击(此处)折叠或打开

  1. function inverse($x)
  2. {
  3.     if (!$x) {
  4.         throw new Exception('Division by zero.');
  5.     }
  6.     else return 1/$x;
  7. }
  8. try {
  9.     echo inverse(5) . "\n";
  10.     echo inverse(0) . "\n";
  11. } catch (Exception $e) {
  12.     echo 'Caught exception: ', $e->getMessage(), "\n";
  13. }
  14. // Continue execution
  15. echo 'Hello World';

而在我研究過 Zend Framework 的做法後,發現它在處理 Exception 上更加聰明。 Zend Framework 在 Controller 中引入一個 Response 物件,所有對瀏覽器的輸出都要經過它 (例如 Header 、 Content Body 等等) ;而這個 Reponse 物件也同時控管著 Exception 是否要被輸出到瀏覽器端,讓程式開發者能有更大的空間處理 Exception 。

以下我簡單把 Zend Framework 在 Response 物件中處理 Exception 的概念整理成一個自製的 Response 類別:

点击(此处)折叠或打开

  1. class Response
  2. {
  3.     private $_exceptions = array();
  4.     private $_renderExceptions = false;
  5.     public function setException(Exception $e)
  6.     {
  7.         $this->_exceptions[] = $e;
  8.     }
  9.     public function getExceptions()
  10.     {
  11.         return $this->_exceptions;
  12.     }
  13.     public function isException()
  14.     {
  15.         return !empty($this->_exceptions);
  16.     }
  17.     public function renderExceptions($flag = null)
  18.     {
  19.         if (null !== $flag) {
  20.             $this->_renderExceptions = $flag ? true : false;
  21.         }
  22.         return $this->_renderExceptions;
  23.     }
  24.     public function sendResponse()
  25.     {
  26.         echo "Header sending...\n";
  27.         $exception = '';
  28.         if ($this->isException() && $this->renderExceptions()) {
  29.             foreach ($this->getExceptions() as $e) {
  30.                 $exception .= $e->getMessage() . "\n";
  31.             }
  32.             echo $exception;
  33.         }
  34.         echo "Body sending...\n";
  35.     }
  36. }

主要的概念很簡單,就是 Response 物件先把 Exception 先收集起來,然後再視狀況如何處理,例如:

点击(此处)折叠或打开

  1. $response = new Response();
  2. $response->renderExceptions(true); // 讓 Exception 呈現出來
  3. try {
  4.     // 這裡處理我們真正要執行的動作
  5.     throw new Exception('TEST'); // 丟出一個測試用的例外
  6. } catch (Exception $e) {
  7.     $response->setException($e); // 收集例外
  8. }
  9. if ($response->isException()) {
  10.     // 可以在這裡記錄 Exception
  11. }
  12. $response->sendResponse(); // 顯示所有結果 (包含 Header, Exception, Body)

透過了 Response 物件來管理 Exception ,就可以不必因為 Exception 而中斷我們的程式碼。

PHP Error 的處理


雖然 Exception 可以用 try…catch 控制程式流程,但 PHP Error 卻不行。

因為一般處理 PHP Error 的方法是透過 set_error_handler,而當執行完自訂的 Error Handler 後,我們卻只能選擇繼續執行下一行程式碼或將程式中斷離開,不然就是要利用全域變數來設定錯誤旗標以達到控制的目的。

点击(此处)折叠或打开

  1. $error = false;
  2. function exceptionErrorHandler($errno, $errstr, $errfile, $errline)
  3. {
  4.     global $error;
  5.     $error = true;
  6.     echo $errstr, "\n";
  7.     return true;
  8. }
  9. set_error_handler("exceptionErrorHandler");
  10. strpos();
  11. if (!$error) {
  12.     echo "Do normal process here.\n";
  13. }
  14. echo "end.\n";

不過 PHP5 也幫我們想好了,我們可以在 Error Handler 裡丟出 ErrorException ,就可以配合前面提到的 Response 物件做到更平順的 Exception 處理:

点击(此处)折叠或打开

  1. function exceptionErrorHandler($errno, $errstr, $errfile, $errline )
  2. {
  3.     throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
  4. }
  5. set_error_handler("exceptionErrorHandler");
  6. $response = new Response();
  7. $response->renderExceptions(true); // 讓 Exception 呈現出來
  8. try {
  9.     // 這裡處理我們真正要執行的動作
  10.     trigger_error('TEST', E_USER_ERROR); // 改用 trigger_error 來丟出測試用錯誤
  11. } catch (Exception $e) {
  12.     $response->setException($e); // 收集例外
  13. }
  14. if ($response->isException()) {
  15.     // 可以在這裡記錄 Exception
  16. }
  17. $response->sendResponse(); // 顯示所有結果 (包含 Header, Exception, Body)

這邊最棒的是 Error Handler 丟出 ErrorException 後, try…catch 就會發生作用,而不再像 set_error_handler 這樣又返回中斷的地方繼續執行,一切就像行雲流水般那麼自然。

結論

一個運作良好的系統必須要對錯誤的發生有最大的掌控權,而不是放任它讓系統墜毀在五里霧之中。
雖然前面提到的處理方式也許不是最佳的,但希望透過這樣的介紹,讓大家能夠思考自己的程式應該如何去處理錯誤這件事。
就寫到這裡吧,收工~

引用自:http://www.jaceju.net/blog/archives/1121/

附录:
php错误级别常量:
php错误处理函数:
php错误参考手册:PHP手册-->函数参考-->影响 PHP行为的扩展-->错误处理


阅读(1572) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~