• 写在前面: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信息。

 
    
 
 
 
 
 
 
 

thinkphp3.2源码(错误和异常处理)的更多相关文章

  1. SpringMVC源码分析-400异常处理流程及解决方法

    本文涉及SpringMVC异常处理体系源码分析,SpringMVC异常处理相关类的设计模式,实际工作中异常处理的实践. 问题场景 假设我们的SpringMVC应用中有如下控制器: 代码示例-1 @Re ...

  2. Spring MVC源码(四) ----- 统一异常处理原理解析

    SpringMVC除了对请求URL的路由处理特别方便外,还支持对异常的统一处理机制,可以对业务操作时抛出的异常,unchecked异常以及状态码的异常进行统一处理.SpringMVC既提供简单的配置类 ...

  3. Spring AMQP 源码分析 05 - 异常处理

    ### 准备 ## 目标 了解 Spring AMQP Message Listener 如何处理异常 ## 前置知识 <Spring AMQP 源码分析 04 - MessageListene ...

  4. Android studio应用导入源码错误This attribute must be localized

    This attribute must be localized 产生原因: 多语言错误,源码中关于语言的显示不能直接赋值,而是需要通过xml来实现: 例如 <TextView android: ...

  5. jsoncpp-src-0.5.0.tar.gz 源码错误!!!!

    近期在做毕设,使用到了JsonCpp0.5.0版本号的源码! 依照网上的安装配置教程,搭建好环境后就能够使用了! 在这里就不浪费空间去将怎样搭建开发环境了!请大家去google一下就好了! 在解析一个 ...

  6. Spring 源码学习系列

    前言 Spring框架之于 JavaEE 程序员来说,犹如锄头之于农民.Java 程序员每天都要使用Spring框架,Spring框架也确实是个可手的工具. 最初使用Spring的时候,我们需要配置m ...

  7. 关于Eclipse部署openfire3.8.2源码的体会

    因为公司要做人际银行的一个项目需要openfire(服务器)+asmack(客户端),所以需要对消息的推送及消息发送知识的积累.所以需要研究xmpp,以前不是很了解这个技术,现在需要学习.首先就得部署 ...

  8. Spring 源码分析-1-启动

    Spring 源码分析-1-启动 在web项目中使用spring的时候,我们会在web.xml中加入如下配置: <listener> <listener-class>org.s ...

  9. win10+vs2008编译比特币1.0版源码总结

    https://zhuanlan.zhihu.com/p/25074960 https://zhuanlan.zhihu.com/p/25095222 总体上是参考这两个链接,感谢大神的分享,但是中间 ...

随机推荐

  1. docker里运行cron的要点笔记

    1.如果用精简的apline或debian:stretch-slim创建的docker,里面可能没有cron模块,需要独立安装 apt-get install -y cron 2.docker里面cr ...

  2. 监控服务zabbix部署

    目录 1. zabbix介绍 2. zabbix特点 3. zabbix配置文件 4. 部署zabbix 4.1 zabbix服务端安装 4.2 zabbix服务端配置 4.3 zabbix服务端we ...

  3. 阿里云云计算助理工程师认证(ACA)

    经过两天的学习(观看视频,阅读官方帮助文档),完成了初级云计算认证. 本次考试难度相对较低,考察内容较为初级 考点主要考察学员是否真正的动手实验过,不局限于视频中讲解的内容,较多的考点为视频中操作演示 ...

  4. mysql数据库总结。

    mysql MySQL语法MySQL采用结构化查询语言SQL (Structured Query Language)语言来操作数据库SQL语句必须以 ; 结束SQL语句分类DDL(数据定义语言): c ...

  5. moviepy的常见用法

    看了,还是自己弄这些方便. #字幕 >>> from moviepy.video.tools.subtitles import SubtitlesClip >>> ...

  6. 【转载】lr运行时设置,每个action 比例

    提供了再脚本运行时所需要的相关选项. 性能测试的关键之一:能否通过脚本来完全模拟用户的行为,可以通过运行设置让脚本运行的更人性化. 1. Run Logic 脚本如何运行,每个action与actio ...

  7. idea去除mybatis的xml那个恶心的绿色背景

    https://my.oschina.net/qiudaozhang/blog/2877536

  8. Vue移动端项目如何使用手机预览调试

  9. Pycharm 主题背景色的配置

    PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具.那么它的主题背景如何设置呢? 具体操作:   步骤一:选择 “file” 菜单下的 “se ...

  10. shell部分面试题

    1.用Shell编程,判断一文件是不是块或字符设备文件,如果是将其拷贝到 /dev 目录下. #!/bin/bash#1.sh#判断一文件是不是字符或块设备文件,如果是将其拷贝到 /dev 目录下#f ...