thinkphp3.2源碼(錯誤和異常處理)

 

  • 寫在前面:tp3.2中每次載入入口文件時都會進行錯誤和異常的捕獲,解讀這一部分代碼能夠對之後的優化頗有好處。
 
  • 處理概覽:
 
 
  •      錯誤捕獲與處理:
  1. 致命錯誤捕獲:
咱們嘗試在 Home/Index/index 下調用一個未定義的函數,會看到這樣的提示頁面:
     咱們能夠看到tp3.2處理了致命異常的輸出,而且生成了一個提示頁面,咱們能夠經過入口文件很容易地找到tp3.2的致命錯誤的捕獲方法        Think/Library/Think/Think.class.php:
 
  1.  
    public static function start()
  2.  
    {
  3.  
    // 註冊AUTOLOAD方法
  4.  
    spl_autoload_register( 'Think\Think::autoload');
  5.  
    // 設定錯誤和異常處理
  6.  
    register_shutdown_function( 'Think\Think::fatalError');
  7.  
    set_error_handler( 'Think\Think::appError');
  8.  
    set_exception_handler( 'Think\Think::appException');
  9.  
     
  10.  
    .........................

tp使用了 register_shutdown_function()來註冊一個在php停止時執行的函數,經過這個回調函數來捕獲了致命異常:
 
fatalError:
 
  1.  
    // 致命錯誤捕獲
  2.  
    public static function fatalError()
  3.  
    {
  4.  
    Log::save();
  5.  
     
  6.  
    if ($e = error_get_last()){
  7.  
    switch ($e[ 'type']) {
  8.  
    case E_ERROR: //一般會顯示出來,也會中斷程序執行
  9.  
    case E_PARSE: //語法解析錯誤
  10.  
    case E_CORE_ERROR: //在PHP啓動時發生的致命錯誤
  11.  
    case E_COMPILE_ERROR: //編譯時發生的致命錯誤,指出腳本的錯誤
  12.  
    case E_USER_ERROR: //用戶產生的錯誤信息
  13.  
    ob_end_clean();
  14.  
    self::halt($e);
  15.  
     
  16.  
    break;
  17.  
    }
  18.  
    }
  19.  
    }
 
在這個方法中,tp使用error_get_last得到當前錯誤,而且使用ob_end_clean 丟掉緩衝區內容,阻止頁面上錯誤信息的輸出。
       爲何ob_end_clean能夠阻止頁面輸出錯誤信息呢?這還得從php的緩衝區提及, 當PHP自身的緩衝區接到指令,指示要輸出緩衝區的內容時,將會把緩衝區內的數據輸出到apache上, apache接受到PHP輸出的數據,而後再把該數據存在到apache自身的緩衝區內,等到輸出 當apache接受到指令,只是要輸出緩衝區的內容時, 將會把緩衝區的內容輸出,返回到瀏覽器。而停止回調是做爲請求的一部分被執行的,所以能夠在它們中進行輸出或者讀取輸出緩衝區,咱們此時用ob_end_clean丟掉緩衝區的內容,就阻止了頁面的輸出顯示。(關於緩衝區:傳送門
      獲取到當前錯誤後,tp講這個錯誤傳遞個halt($e)這個靜態方法,這個方法其實就是tp的錯誤輸出處理了,咱們能夠從「處理概覽」圖中能夠看到,tp根據php不一樣的運行模式進行錯誤信息的處理與顯示:
   halt:
  1.  
    /**
  2.  
    * 錯誤輸出
  3.  
    * @param mixed $error 錯誤
  4.  
    * @return void
  5.  
    */
  6.  
    public static function halt($error)
  7.  
    {
  8.  
    $e = array();
  9.  
    if (APP_DEBUG || IS_CLI) {
  10.  
    //調試模式下輸出錯誤信息
  11.  
    if (!is_array($error)) {
  12.  
    $trace = debug_backtrace();
  13.  
    $e[ 'message'] = $error;
  14.  
    $e[ 'file'] = $trace[ 0][ 'file'];
  15.  
    $e[ 'line'] = $trace[ 0][ 'line'];
  16.  
    ob_start();
  17.  
    debug_print_backtrace();
  18.  
    $e[ 'trace'] = ob_get_clean();
  19.  
    } else {
  20.  
    $e = $error;
  21.  
    }
  22.  
    if (IS_CLI) {
  23.  
    exit((IS_WIN ? iconv( 'UTF-8', 'gbk', $e[ 'message']) : $e[ 'message']) . PHP_EOL . 'FILE: ' . $e[ 'file'] . '(' . $e[ 'line'] . ')' . PHP_EOL . $e[ 'trace']);
  24.  
    }
  25.  
    } else {
  26.  
    //不然定向到錯誤頁面
  27.  
    $error_page = C( 'ERROR_PAGE');
  28.  
    if (! empty($error_page)) {
  29.  
    redirect($error_page);
  30.  
    } else {
  31.  
    $message = is_array($error) ? $error[ 'message'] : $error;
  32.  
    $e[ 'message'] = C( 'SHOW_ERROR_MSG') ? $message : C( 'ERROR_MESSAGE');
  33.  
    }
  34.  
    }
  35.  
    // 包含異常頁面模板
  36.  
    $exceptionFile = C( 'TMPL_EXCEPTION_FILE', null, THINK_PATH . 'Tpl/think_exception.tpl');
  37.  
    include $exceptionFile;
  38.  
    exit;
  39.  
    }
 
        APP_DEBUG 能夠在入口文件中修改,IS_CLI是 經過 php預約義常量 「PHP_SAPI」判斷當前php的運行環境,在框架入口文件ThinkPHP.php中是這樣配置的:
define('IS_CLI', PHP_SAPI == 'cli' ? 1 : 0);  //=='cli' 是在說明php在命令行中運行。
   (運行環境監測: 傳送門)
       halt這個靜態方法內,根據php不一樣的運行環境處理傳遞過來的錯誤,命令行環境就這就退出打印,其餘模式就將錯誤信息返回給模塊頁面顯示。
       調試模式中咱們能夠修改think_exception.tpl來調整咱們的頁面提示,非調試模式你也能夠調整think_exception.tpl模板,tp也給了一個錯誤頁面的配置,這些配置在慣例配置文件裏,咱們能夠自定義錯誤信息,也能夠指定錯誤後顯示的頁面。配置以下:
convention.php:
  1.  
    /* 錯誤設置 */
  2.  
    'ERROR_MESSAGE' => '頁面錯誤!請稍後再試~', //錯誤顯示信息,非調試模式有效
  3.  
    'ERROR_PAGE' => '', // 錯誤定向頁面
  4.  
    'SHOW_ERROR_MSG' => false, // 顯示錯誤信息
     2.自定義錯誤處理:
 
   register_shutdown_down是處理「down」的,set_error_handler是處理「error」的,php的崩潰類型多種多樣,就拿錯誤類型的「E_USER_ERROR」來說,文前調用的一個未定義函數testErr()就是觸發的「down」裏面的 E_USER_ERROR,而咱們經過 trigger_error(‘’,E_USER_ERROR)就是觸發的「error」裏面的E_USER_ERROR,全部說自定義一個錯誤處理是頗有必要的,何況還有「NOTICE」這種類型的錯誤不會停止php執行就不能用「down」處理了呢?
    咱們首先經過trigger_error()手動生成一個錯誤來看看tp是如何處理的,咱們嘗試在 Home/Index/index 裏寫下這樣一句代碼:
trigger_error ( "用戶自定義錯誤信息提示" ,  E_USER_ERROR );
運行結果以下:
 
 
 
從運行結果來看,與以前的致命錯誤"down"相比,這個錯誤提示頁面多了TRACE來顯示代碼執行流程,而且錯誤位置也放在了錯誤信息裏面(這個不重要,這個能夠隨便你拼接的),那麼咱們來看看 tp的自定義 錯誤處理:
appErr:
  1.  
    /**
  2.  
    * 自定義錯誤處理
  3.  
    * @access public
  4.  
    * @param int $errno 錯誤類型
  5.  
    * @param string $errstr 錯誤信息
  6.  
    * @param string $errfile 錯誤文件
  7.  
    * @param int $errline 錯誤行數
  8.  
    * @return void
  9.  
    */
  10.  
    public static function appError($errno, $errstr, $errfile, $errline)
  11.  
    {
  12.  
    switch ($errno) {
  13.  
    case E_ERROR:
  14.  
    case E_PARSE:
  15.  
    case E_CORE_ERROR:
  16.  
    case E_COMPILE_ERROR:
  17.  
    case E_USER_ERROR:
  18.  
    ob_end_clean();
  19.  
    $errorStr = "$errstr " . $errfile . " 第 $errline 行.";
  20.  
    if (C( 'LOG_RECORD')) {
  21.  
    Log::write( "[$errno] " . $errorStr, Log::ERR);
  22.  
    }
  23.  
     
  24.  
    self::halt($errorStr);
  25.  
    break;
  26.  
    default:
  27.  
    $errorStr = "[$errno] $errstr " . $errfile . " 第 $errline 行.";
  28.  
    self::trace($errorStr, '', 'NOTIC');
  29.  
    break;
  30.  
    }
  31.  
    }
 
       首先咱們要知道的是,當前的靜態方法是set_error_handler()的回調方法,這個回調方法就包含了錯誤的error_handler(參數說明見代碼)。這個方法和以前的fatalError相比,不一樣的地方主要有兩個(記錄日誌會在之後的博客中說明):傳給halt()的參數變成了一個字符串(以前down處理了是包含錯誤信息的數組);NOTICE不在是經過halt去顯示了,而是調用了另一個方法,trace();
      咱們先來看看第一個,傳一個字符串給用於顯示錯誤的halt()方法,咱們從上面的halt代碼塊中能夠看到這樣一段:
  1.  
    if (!is_array($error)) {
  2.  
    $trace = debug_backtrace();
  3.  
    $e[ 'message'] = $error;
  4.  
    $e[ 'file'] = $trace[ 0][ 'file'];
  5.  
    $e[ 'line'] = $trace[ 0][ 'line'];
  6.  
    ob_start();
  7.  
    debug_print_backtrace();
  8.  
    $e[ 'trace'] = ob_get_clean();
  9.  
    } else {
  10.  
    $e = $error;
  11.  
    }
若是傳遞過來的參數不是數組,經過處理後$e就多一個成員['trace'],而咱們在錯誤模板中能夠發現,這個成員就是用於顯示咱們的代碼執行流程(追溯)的:
think_exception.tpl:
  1.  
    <?php if( isset($e[ 'trace'])) { ?>
  2.  
    <div class="info">
  3.  
    <div class="title">
  4.  
    <h3>TRACE</h3>
  5.  
    </div>
  6.  
    <div class="text">
  7.  
    <p><?php echo nl2br($e['trace']);?></p>
  8.  
    </div>
  9.  
    </div>
  10.  
    <?php }?>
     那tp是如何去追溯這個代碼執行的呢?實際上是經過debug_backtrace()這個函數,debug_backtrace()產生一條回溯追蹤,說簡單點,就是個人這個錯誤是如何運行到這裏來的(因爲是回溯,通常返回的第一條就是產生錯誤的地方)。而後經過debug_print_backtrace()打印信息,在經過ob_get_clean獲得緩衝區內容並關閉緩衝區阻止瀏覽器的輸出,最後就在模板裏判斷是否存在$e['trace']來作輸出顯示。(不得不說,debug_backtrace是個調試神器)
若是是NOTICE級別的錯誤,就傳到了trace()方法,作日誌記錄。
trace:
  1.  
    /**
  2.  
    * 添加和獲取頁面Trace記錄
  3.  
    * @param string $value 變量
  4.  
    * @param string $label 標籤
  5.  
    * @param string $level 日誌級別(或者頁面Trace的選項卡)
  6.  
    * @param boolean $record 是否記錄日誌
  7.  
    * @return void|array
  8.  
    */
  9.  
    public static function trace($value = '[think]', $label = '', $level = 'DEBUG', $record = false)
  10.  
    {
  11.  
    static $_trace = array();
  12.  
    if ( '[think]' === $value) {
  13.  
    // 獲取trace信息
  14.  
    return $_trace;
  15.  
    } else {
  16.  
    $info = ($label ? $label . ':' : '') . print_r($value, true);
  17.  
    $level = strtoupper($level);
  18.  
     
  19.  
    if ((defined( 'IS_AJAX') && IS_AJAX) || !C( 'SHOW_PAGE_TRACE') || $record) {
  20.  
    Log::record($info, $level, $record);
  21.  
    } else {
  22.  
    if (! isset($_trace[$level]) || count($_trace[$level]) > C( 'TRACE_MAX_RECORD')) {
  23.  
    $_trace[$level] = array();
  24.  
    }
  25.  
    $_trace[$level][] = $info;
  26.  
    }
  27.  
    }
  28.  
    }

  • 異常處理
tp自定義了異常的處理,使用set_exception_handler()函數,設置了一個appException方法處理異常,咱們嘗試拋出一個異常,看tp的運行結果:
 
throw new \Exception('拋出一個異常')
   
 
能夠看到運行結果和「error」級別的處理很相似,咱們能夠看看使用set_exception_handler()設置的appException()方法:
  1.  
    public static function appException($e)
  2.  
    {
  3.  
    $error = array();
  4.  
    $error[ 'message'] = $e->getMessage();
  5.  
    $trace = $e->getTrace();
  6.  
    if ( 'E' == $trace[ 0][ 'function']) {
  7.  
    $error[ 'file'] = $trace[ 0][ 'file'];
  8.  
    $error[ 'line'] = $trace[ 0][ 'line'];
  9.  
    } else {
  10.  
    $error[ 'file'] = $e->getFile();
  11.  
    $error[ 'line'] = $e->getLine();
  12.  
    }
  13.  
    $error[ 'trace'] = $e->getTraceAsString();
  14.  
    Log::record($error[ 'message'], Log::ERR);
  15.  
    // 發送404信息
  16.  
    header( 'HTTP/1.1 404 Not Found');
  17.  
    header( 'Status:404 Not Found');
  18.  
    self::halt($error);
  19.  
    }
首先咱們要知道,$e就是當前的異常對象,$e能夠調用該異常對象是方法,其中$e->getTrace()是追蹤包含異常信息的數組,追蹤信息中包含觸發異常的函數,tp判斷觸發該異常的函數是否是tp自帶的 E()函數,從而組裝異常信息發送給halt顯示,咱們能夠看到傳遞給halt是經過$e->getTraceAsString()獲取的字符串,因此halt後面又會用debug_backtrace()追溯異常,最後在頁面上生成TRACE信息。
相關文章
相關標籤/搜索