2012年10月12日 星期五

PHP的Exception與錯誤處理

        會寫這篇文章,主要是為了讓學生能夠對於PHP 5的錯誤處理機制有更進一步的認識,而非單純依靠程式產生的方式來處理,甚至不處理自己程式的錯誤。

在PHP 5之前,PHP對於錯誤處理(Error)採用以下的方法:
<?php
  //自訂錯誤處理函數,PHP預設錯誤常數,錯誤訊息,錯誤發生的檔案,錯誤發生的行號
  function custom_error($errno, $errstr, $errfile, $errline){
     //如果回傳true,不要讓PHP來正常處理錯誤(不拋出)
     return true;
     //如果回傳false,則代表讓PHP來正常處理錯誤(拋出)
     //return false;
  }

  //設定錯誤需要報告的等級
  error_reporting(E_ERROR|E_USER_ERROR|E_WARNING|E_USER_WARNING|E_NOTICE|E_USER_NOTICE);
  //如果不設定就讓PHP來正常處理錯誤(拋出)
  set_error_handler("custom_error");

  //因為customer_error傳回true則
  echo $ary['err'].'<br />';
  echo 'after error'.'<br />';
?>

        error_reporting函數所代表的,是那些錯誤等級需要PHP將其處理與呈現,如果不加上這一句,代表所有錯誤等級皆會處理,也就是錯誤等級E_ALL,錯誤等級列表如下:http://tw2.php.net/manual/en/errorfunc.constants.php
如果要針對錯誤進行相關處理,可以藉由set_error_handler來指定錯誤處理函數,而錯誤處理函數的帶入參數為:自訂錯誤處理函數、PHP預設錯誤常數、錯誤訊息、錯誤發生的檔案、錯誤發生的行號。

        上述的範例中,「$ary['err']」這一行一定會錯,所以如果錯誤處理函數回傳為true,代表不要讓PHP來正常處理錯誤(不拋出),如果回傳false,則代表讓PHP來正常處理錯誤(拋出),當然也可以在錯誤處理函數裡針對不同等級的錯誤來進行相關處理,如下範例:
<?php
  function custom_error($errno, $errstr, $errfile, $errline){
     //
     $stop_flag = false;
     //可以依照錯誤的等級個別指定處理方式
     switch ($errno){
      case E_USER_ERROR:
         echo 'E_USER_ERROR<br/>';
         $stop_flag = true;
         break;
      case E_USER_WARNING:
         echo 'E_USER_WARNING<br/>';
         $stop_flag = true;
         break;
      case E_USER_NOTICE:
         echo 'E_USER_NOTICE<br/>';
         $stop_flag = true;
         break;
      case E_ERROR:
         echo 'E_ERROR<br/>';
         $stop_flag = true;
         break;
      case E_WARNING:
         echo 'E_WARNING<br/>';
         $stop_flag = true;
         break;
      case E_NOTICE:
         echo 'E_NOTICE<br/>';
         $stop_flag = true;
         break;
      default:
         echo 'other error<br/>';
         $stop_flag = true;
     }
     return $stop_flag;
  }

  //
  error_reporting(E_ERROR|E_USER_ERROR|E_WARNING|E_USER_WARNING|E_NOTICE|E_USER_NOTICE);
  set_error_handler("custom_error");

  //
  echo $ary['err'].'<br />';
  echo 'after error'.'<br />';
?>

         而PHP 5之後,開始加入Exception的例外處理模式,也就是其他語言中所熟知的Try…Catch..語法,但是在目前的PHP 5裡尚未加入Finally的語法,如果要在PHP裡讓Exception可以抓到錯誤,則必須拋出Exception,如下面範例中的「throw new Exception("custom exception!");」,但是針對之前我們所介紹的錯誤,卻是無法進行抓取:

<?php
  //try...catch 用法
  try{
      throw new Exception("custom exception!");
  } catch (Exception $e) {
      echo "例外的訊息字串 : " . $e->getMessage()."<br />";
      echo "產生例外的檔案 : " . $e->getFile()."<br />";
      echo "產生例外的行號 : " . $e->getLine()."<br />";
      echo "產生例外的程式碼 : " . $e->getCode()."<br />";
      echo "例外的堆疊追蹤陣列 : ";
      print_r($e->getTrace());
      echo "<br />";
      echo "例外的堆疊追蹤字串 : " . $e->getTraceAsString()."<br />";
      echo "例外的表示字串 : " . $e->__toString()."<br />";
  }
  echo 'after exception...'.'<br/>';


  //但是error無法使用
  try{
      echo $ary['err'];
  } catch (Exception $e) {
      echo $e->getMessage()."<br />";
  }
?>

        所以我們可以在錯誤處理函數裡拋出ErrorException,這樣不管是怎樣的錯誤等級,都可以使用Exception來進行處理,當然也可以在錯誤處理函數裡區分錯誤等級,不一定所有錯誤等級都要拋出ErrorException。
<?php
  function custom_error($errno, $errstr, $errfile, $errline){
     //發生Error就直接拋出Exception,以便後續可以使用try~catch處理
     //錯誤訊息,內部代碼,PHP預設錯誤常數,錯誤發生的檔案,錯誤發生的行號
     throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
  }

  //
  error_reporting(E_ERROR|E_USER_ERROR|E_WARNING|E_USER_WARNING|E_NOTICE|E_USER_NOTICE);
  set_error_handler("custom_error");

  //
  try{
   echo $ary['err'];
  } catch (Exception $e) {
   echo $e->getMessage().'<br />';
  }
  echo 'after error exception...'."<br />";
?>

        當然可以使用PHP 5之後的物件導向的類別,將其包裝成可重覆使用的物件,以減少程式碼的產生,也可以在該類別中加上記錄的函數,將其記錄到所指定的文件裡,以下範例是每天會產生出一個檔案來記錄
<?php
  class error_handler{
    public function error_handler(){
   
    }
   
    public static function custom_error($errno, $errstr, $errfile, $errline){
         throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    }
   
    public static function record(Exception $e){
     //先指定時區
     date_default_timezone_set('Asia/Taipei');
   
     //格式化錯誤訊息
     $err_str = sprintf("錯誤發生日期: %s[錯誤訊息: %s]\r\n 錯誤檔案: %s\r\n 錯誤行號 : %s\r\n 錯誤堆疊追蹤字串: %s\r\n-------------\r\n",
       date("Y-m-d H:m:s"), $e->getMessage(), $e->getFile(), $e->getLine(), $e->getTraceAsString());
   
     //在檔案內記錄
     error_log($err_str, 3, "C:\\error_log\\".date("Ymd").".log");
    }
  }
?>


<?php require_once 'error_handler.php';?>
<?php
  $error_handler = set_error_handler("error_handler::custom_error");
  try{
   echo $ary['err'];
  } catch (Exception $e) {
   error_handler::record($e);
   echo '系統發生錯誤!';
  }
?>

轉貼請註明出處,最好直接使用聯結轉貼!Thanks~
作者: Samuel - 林靖傑
日期:2012/10/12

沒有留言:

張貼留言