thinkphp3.2源码------ 错误和异常处理
来源:互联网 发布:得力3960考勤机软件 编辑:程序博客网 时间:2024/06/14 01:10
- 写在前面:tp3.2中每次载入入口文件时都会进行错误和异常的捕获,解读这一部分代码可以对以后的优化很有好处。
- 处理概览:
- 错误捕获与处理:
- 致命错误捕获:
我们尝试在 Home/Index/index 下调用一个未定义的函数,会看到这样的提示页面:
我们可以看到tp3.2处理了致命异常的输出,并且生成了一个提示页面,我们可以通过入口文件很容易地找到tp3.2的致命错误的捕获方法 Think/Library/Think/Think.class.php:
public static function start() { // 注册AUTOLOAD方法 spl_autoload_register('Think\Think::autoload'); // 设定错误和异常处理 register_shutdown_function('Think\Think::fatalError'); set_error_handler('Think\Think::appError'); set_exception_handler('Think\Think::appException');.........................
tp使用了 register_shutdown_function()来注册一个在php中止时执行的函数,通过这个回调函数来捕获了致命异常:
fatalError:
// 致命错误捕获 public static function fatalError() { Log::save(); if ($e = error_get_last()){ switch ($e['type']) { case E_ERROR: //通常会显示出来,也会中断程序执行 case E_PARSE: //语法解析错误 case E_CORE_ERROR: //在PHP启动时发生的致命错误 case E_COMPILE_ERROR: //编译时发生的致命错误,指出脚本的错误 case E_USER_ERROR: //用户产生的错误信息 ob_end_clean(); self::halt($e); break; } } }
为什么ob_end_clean可以阻止页面输出错误信息呢?这还得从php的缓冲区说起,当PHP自身的缓冲区接到指令,指示要输出缓冲区的内容时,将会把缓冲区内的数据输出到apache上, apache接受到PHP输出的数据,然后再把该数据存在到apache自身的缓冲区内,等到输出当apache接受到指令,只是要输出缓冲区的内容时, 将会把缓冲区的内容输出,返回到浏览器。而中止回调是作为请求的一部分被执行的,因此可以在它们中进行输出或者读取输出缓冲区,我们此时用ob_end_clean丢掉缓冲区的内容,就阻止了页面的输出显示。(关于缓冲区:传送门)
获取到当前错误后,tp讲这个错误传递个halt($e)这个静态方法,这个方法其实就是tp的错误输出处理了,我们可以从“处理概览”图中可以看到,tp根据php不同的运行模式进行错误信息的处理与显示:
halt:
/** * 错误输出 * @param mixed $error 错误 * @return void */ public static function halt($error) { $e = array(); if (APP_DEBUG || IS_CLI) { //调试模式下输出错误信息 if (!is_array($error)) { $trace = debug_backtrace(); $e['message'] = $error; $e['file'] = $trace[0]['file']; $e['line'] = $trace[0]['line']; ob_start(); debug_print_backtrace(); $e['trace'] = ob_get_clean(); } else { $e = $error; } if (IS_CLI) { exit((IS_WIN ? iconv('UTF-8', 'gbk', $e['message']) : $e['message']) . PHP_EOL . 'FILE: ' . $e['file'] . '(' . $e['line'] . ')' . PHP_EOL . $e['trace']); } } else { //否则定向到错误页面 $error_page = C('ERROR_PAGE'); if (!empty($error_page)) { redirect($error_page); } else { $message = is_array($error) ? $error['message'] : $error; $e['message'] = C('SHOW_ERROR_MSG') ? $message : C('ERROR_MESSAGE'); } } // 包含异常页面模板 $exceptionFile = C('TMPL_EXCEPTION_FILE', null, THINK_PATH . 'Tpl/think_exception.tpl'); include $exceptionFile; exit; }
define('IS_CLI', PHP_SAPI == 'cli' ? 1 : 0); //=='cli' 是在说明php在命令行中运行。(运行环境监测:传送门)
halt这个静态方法内,根据php不同的运行环境处理传递过来的错误,命令行环境就这就退出打印,其他模式就将错误信息返回给模块页面显示。
调试模式中我们可以修改think_exception.tpl来调整我们的页面提示,非调试模式你也可以调整think_exception.tpl模板,tp也给了一个错误页面的配置,这些配置在惯例配置文件里,我们可以自定义错误信息,也可以指定错误后显示的页面。配置如下:
convention.php:
/* 错误设置 */ 'ERROR_MESSAGE' => '页面错误!请稍后再试~', //错误显示信息,非调试模式有效 'ERROR_PAGE' => '', // 错误定向页面 '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:
/** * 自定义错误处理 * @access public * @param int $errno 错误类型 * @param string $errstr 错误信息 * @param string $errfile 错误文件 * @param int $errline 错误行数 * @return void */ public static function appError($errno, $errstr, $errfile, $errline) { switch ($errno) { case E_ERROR: case E_PARSE: case E_CORE_ERROR: case E_COMPILE_ERROR: case E_USER_ERROR: ob_end_clean(); $errorStr = "$errstr " . $errfile . " 第 $errline 行."; if (C('LOG_RECORD')) { Log::write("[$errno] " . $errorStr, Log::ERR); } self::halt($errorStr); break; default: $errorStr = "[$errno] $errstr " . $errfile . " 第 $errline 行."; self::trace($errorStr, '', 'NOTIC'); break; } }
我们先来看看第一个,传一个字符串给用于显示错误的halt()方法,我们从上面的halt代码块中可以看到这样一段:
if (!is_array($error)) { $trace = debug_backtrace(); $e['message'] = $error; $e['file'] = $trace[0]['file']; $e['line'] = $trace[0]['line']; ob_start(); debug_print_backtrace(); $e['trace'] = ob_get_clean(); } else { $e = $error; }如果传递过来的参数不是数组,通过处理后$e就多一个成员['trace'],而我们在错误模板中可以发现,这个成员就是用于显示我们的代码执行流程(追溯)的:
think_exception.tpl:
<?php if(isset($e['trace'])) {?><div class="info"><div class="title"><h3>TRACE</h3></div><div class="text"><p><?php echo nl2br($e['trace']);?></p></div></div><?php }?>
那tp是如何去追溯这个代码执行的呢?其实是通过debug_backtrace()这个函数,debug_backtrace()产生一条回溯追踪,说简单点,就是我的这个错误是如何运行到这里来的(由于是回溯,一般返回的第一条就是产生错误的地方)。然后通过debug_print_backtrace()打印信息,在通过ob_get_clean得到缓冲区内容并关闭缓冲区阻止浏览器的输出,最后就在模板里判断是否存在$e['trace']来做输出显示。(不得不说,debug_backtrace是个调试神器)
如果是NOTICE级别的错误,就传到了trace()方法,做日志记录。
trace:
/** * 添加和获取页面Trace记录 * @param string $value 变量 * @param string $label 标签 * @param string $level 日志级别(或者页面Trace的选项卡) * @param boolean $record 是否记录日志 * @return void|array */ public static function trace($value = '[think]', $label = '', $level = 'DEBUG', $record = false) { static $_trace = array(); if ('[think]' === $value) { // 获取trace信息 return $_trace; } else { $info = ($label ? $label . ':' : '') . print_r($value, true); $level = strtoupper($level); if ((defined('IS_AJAX') && IS_AJAX) || !C('SHOW_PAGE_TRACE') || $record) { Log::record($info, $level, $record); } else { if (!isset($_trace[$level]) || count($_trace[$level]) > C('TRACE_MAX_RECORD')) { $_trace[$level] = array(); } $_trace[$level][] = $info; } } }
- 异常处理
tp自定义了异常的处理,使用set_exception_handler()函数,设置了一个appException方法处理异常,我们尝试抛出一个异常,看tp的运行结果:
throw new \Exception('抛出一个异常')
可以看到运行结果和“error”级别的处理很类似,我们可以看看使用set_exception_handler()设置的appException()方法:
public static function appException($e) { $error = array(); $error['message'] = $e->getMessage(); $trace = $e->getTrace(); if ('E' == $trace[0]['function']) { $error['file'] = $trace[0]['file']; $error['line'] = $trace[0]['line']; } else { $error['file'] = $e->getFile(); $error['line'] = $e->getLine(); } $error['trace'] = $e->getTraceAsString(); Log::record($error['message'], Log::ERR); // 发送404信息 header('HTTP/1.1 404 Not Found'); header('Status:404 Not Found'); self::halt($error); }
首先我们要知道,$e就是当前的异常对象,$e可以调用该异常对象是方法,其中$e->getTrace()是追踪包含异常信息的数组,追踪信息中包含触发异常的函数,tp判断触发该异常的函数是不是tp自带的 E()函数,从而组装异常信息发送给halt显示,我们可以看到传递给halt是通过$e->getTraceAsString()获取的字符串,所以halt后面又会用debug_backtrace()追溯异常,最后在页面上生成TRACE信息。
阅读全文
1 0
- thinkphp3.2源码------ 错误和异常处理
- 错误和异常处理
- 错误和异常处理
- 异常处理和错误
- 异常和错误处理
- 错误和异常处理
- thinkphp3.2和Workerman 源码整合
- c# 错误和异常处理
- Delphi--异常和错误处理
- C#错误和异常处理
- PHP 错误和异常处理
- php异常和错误处理
- c++错误和异常处理
- PHP错误和异常处理
- php异常和错误处理
- python错误和异常处理
- python错误和异常处理
- 编程中的错误处理和异常处理
- C++关键字之dynamic_cast
- Java Swing 图形界面开发(目录)
- 北方大学多校训练第12场C题
- 微信小程序-网络篇
- Java中的字符串比较,按照使用习惯进行比较
- thinkphp3.2源码------ 错误和异常处理
- clion下配置Qt开发环境
- Android数据存储-将数据存储到文件中2017—05-30
- C++11之lambda
- VB .NET 将DataSet修改后的数据使用update写到数据表中
- Deepin15.1下搭建GTK+3开发环境
- python浅拷贝和深拷贝
- Docker:Centos 安装Redis
- return "cartSuccess" 和 return "redirect:/cart/cart.html" 的区别