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信息。
相关文章
相关标签/搜索