▪ 前言
在 Yii2 使用中,我们发现一但程序出现错误,Yii2 就能自动显示其专用的错误提示界面,和我们写原生态时出现的错误提示界面完全不一样。它究竟是怎么做到的呢:是在哪里设置监听的?亦或在哪里用的try catch?”。
其实 PHP 有自己专用的错误处理 API, 当程序出现问题时,可以自动调用指定函数。而 Yii2 正是利用这一点,在其启动的时候,使用 PHP 内置的 set_error_handler
将自己的错误处理注册进步并关闭 PHP 自身的错误显示。
▪ Yii2 错误自定义处理
在 官方教程 中,它告诉我们要开启自定义的错误,需要进行如下配置组件:
return [
// ...
'components' => [
// ...
'errorHandler' => [
'errorAction' => 'site/error',
],
]
// ...
];
'errorAction' => 'site/error'
表示一但发生错误,那么 Yii2 将调用SiteController
控制器的errorAction
方法,该方法是你自己自定义显示错误的入口。
实际开发中很多新手发现虽然填写了这个配置,但是发现 Yii2 并没有如我们所想的进入到 SiteController
控制器的 errorAction
方法,究竟是什么原因?看下面我们一步一步来解释。
▪ Yii2 错误注册机制
X. 预定义开启错误处理常量
下面的变量将控制是否开启错误处理,默认为开启,你可以通过设置该变量值关闭错误日志
# \yii\BaseYii.php
/**
* This constant defines whether error handling should be enabled. Defaults to true.
*/
defined('YII_ENABLE_ERROR_HANDLER') or define('YII_ENABLE_ERROR_HANDLER', true);
X. 预定义默认组件 errorHandler
下面的函数主要是设置错误处理的默认类,如果你的配置文件中没有设置 errorHandler
的 class
,那么将直接使用 'yii\web\ErrorHandler'
类对象进行处理:
# yii2\web\Application.php
/**
* @inheritdoc
*/
public function coreComponents()
{
return array_merge(parent::coreComponents(), [
'request' => ['class' => 'yii\web\Request'],
'response' => ['class' => 'yii\web\Response'],
'session' => ['class' => 'yii\web\Session'],
'user' => ['class' => 'yii\web\User'],
'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
]);
}
X. 运行时初始化注册错误处理机制流程
应用入口文件 index.php
初始化时将调用基类的 yii\base\Application.php
构造函数,其中包含了开始注册错误处理机制:
# index.php
// ...
(new yii\web\Application($config))->run();
# yii\base\Application.php
public function __construct($config = [])
{
Yii::$app = $this;
$this->setInstance($this);
$this->state = self::STATE_BEGIN;
$this->preInit($config);
// 开始注册错误处理机制
$this->registerErrorHandler($config);
Component::__construct($config);
}
// ...
/**
* 注册错误处理组件
* @param array $config application config
*/
protected function registerErrorHandler(&$config)
{
if (YII_ENABLE_ERROR_HANDLER) {
// 获取错误处理组件信息
if (!isset($config['components']['errorHandler']['class'])) {
echo "Error: no errorHandler component is configured.\n";
exit(1);
}
$this->set('errorHandler', $config['components']['errorHandler']);
unset($config['components']['errorHandler']);
// 获取错误处理对象并注册错误处理钩子
$this->getErrorHandler()->register();
}
}
上述的 $this->getErrorHandler()->register();
将调用如下代码:
# yii\base\ErrorHandler
/**
* Register this error handler
*/
public function register()
{
// 关闭 PHP 原始错误提示
ini_set('display_errors', false);
// 代码的 $this 表示 yii\base\ErrorHandler 的类对象
// 实际情况 Yii2 是构建了 yii\web\ErrorHandler 的类对象,其继承自 yii\base\ErrorHandler 类
// 注册异常的处理钩子
set_exception_handler([$this, 'handleException']);
// 注册错误的处理钩子
if (defined('HHVM_VERSION')) {
set_error_handler([$this, 'handleHhvmError']);
} else {
set_error_handler([$this, 'handleError']);
}
if ($this->memoryReserveSize > 0) {
$this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
}
register_shutdown_function([$this, 'handleFatalError']);
}
由于组件里默认
errorHandler
的处理类是yii\web\ErrorHandler
,所以上述代码set_exception_handler([$this, 'handleException']);
表示调用yii\web\ErrorHandler
类对象的handleException
方法,同理set_error_handler([$this, 'handleError']);
将类对象调用handleError
来显示错误
通过上面的方法,我们能看到,Yii2 通过 PHP 异常处理函数 set_exception_handler
设置处理异常的方法,通过错误处理函数 set_error_handler
设置了处理错误的方法。当有代码中有异常或者错误设置的时候,如果上层没有进一步的异常处理机制,就会被整个全局函数捕捉,并加以处理。
▪ Yii2 错误核心处理方法
在 Yii2 错误自定义处理 中我们了解了 Yii2 错误注册的原理并知道错误的发生后,Yii2 将调用 yii\web\ErrorHandler
类对象的 handleException
和 handleError
方法。
其实 yii\web\ErrorHandler
类中并没有 handleException
和 handleError
方法,但是在其基类 yii\base\ErrorHandler
有,这两个函数最终都将调用 yii\web\ErrorHandler
的 renderException
方法,代码如下:
# yii\web\ErrorHandler
/**
* Renders the exception.
* @param \Exception $exception the exception to be rendered.
*/
protected function renderException($exception)
{
// ...
// 注意:控制错误是否能自定义的关键
// !YII_DEBUG 表示了如果你现在是在 Debug 模式下,那么不能自定义错误
// 即使你配置了组件的 'errorHandler' => ['errorAction' => 'site/error'] 参数
$useErrorView = $response->format === Response::FORMAT_HTML && (!YII_DEBUG || $exception instanceof UserException);
// 传递到自定义错误处理方法
if ($useErrorView && $this->errorAction !== null) {
$result = Yii::$app->runAction($this->errorAction);
if ($result instanceof Response) {
$response = $result;
} else {
$response->data = $result;
}
}
// ...
}
在最终的错误显示方法 renderException
我们可以看出:自定义的错误需特定的情况下才能生效