源码分析(四)—错误及异常处理篇

33次阅读

共计 4635 个字符,预计需要花费 12 分钟才能阅读完成。

源码分析
错误及异常处理机制
错误及异常处理机制文件是 /thinkphp/library/think/Error.php,在框架引导文件的的基础文件 base.php 中注册(不知道的可以去看《《源码分析(二)—入口篇》》),通过 thinkError::register() 进行的注册。

/**
* 注册异常处理
* @access public
* @return void
*/
public static function register()
{
error_reporting(E_ALL);
set_error_handler([__CLASS__, ‘appError’]);
set_exception_handler([__CLASS__, ‘appException’]);
register_shutdown_function([__CLASS__, ‘appShutdown’]);
}

该方法做了四件事情:

设置报错级别 E_ALL 为 E_STRICT 所有报错。
设置错误处理函数,set_error_handler([__CLASS__, ‘appError’])
设置异常处理函数,set_exception_handler([__CLASS__, ‘appException’]);
设置程序异常终止处理函数,register_shutdown_function([__CLASS__, ‘appShutdown’]);

PHP 报错级别
php 的报错级别有:E_STRICT,E_ALL,E_USER_WARNING 等,具体可查看 [php 预定义常量](http://php.net/manual/zh/erro…。
错误处理函数
thinkphp 中注册了 thinkError::appError() 方法对错误进行处理。

/**
* 错误处理
* @access public
* @param integer $errno 错误编号
* @param integer $errstr 详细错误信息
* @param string $errfile 出错的文件
* @param integer $errline 出错行号
* @return void
* @throws ErrorException
*/
public static function appError($errno, $errstr, $errfile = ”, $errline = 0)
{
$exception = new ErrorException($errno, $errstr, $errfile, $errline);

// 符合异常处理的则将错误信息托管至 think\exception\ErrorException
if (error_reporting() & $errno) {
throw $exception;
}

self::getExceptionHandler()->report($exception);
}

在 appError 方法中,把符合异常处理的则将错误信息托管至系统的 ErrorException,其他的异常通过 thinkexceptionHandle 进行处理。

//think\exception\ErrorException 文件

/**
* ThinkPHP 错误异常
* 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误
* 除开从 think\Exception 继承的功能
* 其他和 PHP 系统 \ErrorException 功能基本一样
*/
class ErrorException extends Exception
{
/**
* 用于保存错误级别
* @var integer
*/
protected $severity;

/**
* 错误异常构造函数
* @param integer $severity 错误级别
* @param string $message 错误详细信息
* @param string $file 出错文件路径
* @param integer $line 出错行号
* @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组
*/
public function __construct($severity, $message, $file, $line, array $context = [])
{
$this->severity = $severity;
$this->message = $message;
$this->file = $file;
$this->line = $line;
$this->code = 0;

empty($context) || $this->setData(‘Error Context’, $context);
}

/**
* 获取错误级别
* @return integer 错误级别
*/
final public function getSeverity()
{
return $this->severity;
}
}

errorException 设置错误级别,错误信息,出错文件路径,行号,上下文。
对 exception 进行处理的是 thinkexceptionHandle 的 report() 方法:self::getExceptionHandler()->report($exception);

//self::getExceptionHandler()

/**
* 获取异常处理的实例
* @access public
* @return Handle
*/
public static function getExceptionHandler()
{
static $handle;

if (!$handle) {
// 异常处理 handle
$class = Config::get(‘exception_handle’);

if ($class && is_string($class) && class_exists($class) &&
is_subclass_of($class, “\\think\\exception\\Handle”)
) {
$handle = new $class;
} else {
$handle = new Handle;

if ($class instanceof \Closure) {
$handle->setRender($class);
}

}
}

return $handle;
}

这里有一个关键的地方是:static $handle; 声明该变量是静态变量时候,当赋值给该变量后,函数调用结束后不会销毁,直到脚本结束才会销毁。
这个逻辑就是判断 $handle 是否已经赋值,没有赋值,获取默认配置文件是否设置处理 handle,如果设置,这个 handle 必须是 \think\exception\Handle 的子类(is_subclass_of($class, “\think\exception\Handle”)),如果没有设置,那么用默认的 thinkexceptionHandle 调用 report 方法进行处理, 记录到日志文件中。

/**
* Report or log an exception.
*
* @param \Exception $exception
* @return void
*/
public function report(Exception $exception)
{
if (!$this->isIgnoreReport($exception)) {
// 收集异常数据
if (App::$debug) {
$data = [
‘file’ => $exception->getFile(),
‘line’ => $exception->getLine(),
‘message’ => $this->getMessage($exception),
‘code’ => $this->getCode($exception),
];
$log = “[{$data[‘code’]}]{$data[‘message’]}[{$data[‘file’]}:{$data[‘line’]}]”;
} else {
$data = [
‘code’ => $this->getCode($exception),
‘message’ => $this->getMessage($exception),
];
$log = “[{$data[‘code’]}]{$data[‘message’]}”;
}

if (Config::get(‘record_trace’)) {
$log .= “\r\n” . $exception->getTraceAsString();
}

Log::record($log, ‘error’);
}
}

把 errorException 的数据组装成对应的字符串,写入日志。
异常处理函数
thinkphp 中注册了 thinkError::appException() 方法对错误进行处理。

/**
* 异常处理
* @access public
* @param \Exception|\Throwable $e 异常
* @return void
*/
public static function appException($e)
{
if (!$e instanceof \Exception) {
$e = new ThrowableError($e);
}

$handler = self::getExceptionHandler();
$handler->report($e);

if (IS_CLI) {
$handler->renderForConsole(new ConsoleOutput, $e);
} else {
$handler->render($e)->send();
}
}

方法和 appError 处理差不多,基本都是通过获取 ExceptionHandle 再调用 handle 的 report 方法,但是多了一步把异常呈现,如果是命令行写到命令行输出,如果是 web 的就把错误信息通过 reponse 响应返回客户端。
异常中止时执行的函数
thinkphp 中注册了 thinkError::appShutdown() 方法对错误进行处理。

/**
* 异常中止处理
* @access public
* @return void
*/
public static function appShutdown()
{
// 将错误信息托管至 think\ErrorException
if (!is_null($error = error_get_last()) && self::isFatal($error[‘type’])) {
self::appException(new ErrorException(
$error[‘type’], $error[‘message’], $error[‘file’], $error[‘line’]
));
}

// 写入日志
Log::save();
}

通过 error_get_last() 获取最后抛出的错误,把信息托管至 thinkErrorException,在通过异常处理函数进行记录信息。最后写入日志。
总结
整体整个错误处理机制都是通过获取 ExceptionHandle 再调用 handle 的 report 方法,但是多了一步把异常呈现,如果是命令行写到命令行输出,如果是 web 的就把错误信息通过 reponse 响应返回客户端。默认的处理 handle 是 thinkexceptionHandle,当然也可以自定义 handle,但是必须是 thinkexceptionHandle 的子类,通过 self::getExceptionHandler 的 is_subclass_of($class, “\think\exception\Handle”) 可以知。

正文完
 0