Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 899|回复: 0

thinkphp3.2源码(错误和异常处理)

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-7-5 15:49:31 | 显示全部楼层 |阅读模式

     

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

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-29 02:56 , Processed in 0.072162 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表