FROM : http://tech.lubanr.com/2015/12/12/yii2-0框架的错误和异常处理机制/
在应用开发中,错误和异常处理机制是一块比较重要的模块。yii框架有专门的模块来进行错误和异常处理,本文尝试从yii2.0的源码出发,对yii框架的错误和异常处理机制做一个说明。
yii2.0中,错误和异常处理最先接触到的就是 frontend/config/main.php 中的 component中的一项配置
'errorHandler' => ['errorAction'=>'site/error']
我们先记下这个配置,然后来看看yii框架的启动过程以及错误和异常处理在yii框架中的注册过程。
yii框架的入口页面只有一个: frontend/web/index.php, 所有的访问都会经过nginx重写到这个脚本上(静态文件除外)。
该文件的内容如下:
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
require(__DIR__ . '/../../vendor/autoload.php');
require(__DIR__ . '/../../vendor/yiisoft/yii2/Yii.php');
require(__DIR__ . '/../../common/config/bootstrap.php');
require(__DIR__ . '/../config/bootstrap.php');
$config = yiihelpersArrayHelper::merge(
require(__DIR__ . '/../../common/config/main.php'),
require(__DIR__ . '/../../common/config/main-local.php'),
require(__DIR__ . '/../config/main.php'),
require(__DIR__ . '/../config/main-local.php')
);
$application = new yiiwebApplication($config);
$application->run();
可以看出,$application是核心所在,我们来看看$application都干了些什么
(yiiaseApplication 195行)
yiiwebApplication在初始化的时候,会调用基类 yiiaseApplication的init函数,在init中,有这样一行代码:(第204行)
$this->registerErrorHandler($config);
改行的作用是在application中注册error和exception处理。
protected function registerErrorHandler(&$config)
{
if (YII_ENABLE_ERROR_HANDLER) {
if (!isset($config['components']['errorHandler']['class'])) {
echo "Error: no errorHandler component is configured.
";
exit(1);
}
$this->set('errorHandler', $config['components']['errorHandler']);
unset($config['components']['errorHandler']);
$this->getErrorHandler()->register();
}
}
registerErrorHandler方法的定义如上。$config中的components[‘errorHandler’][‘class’]在webApplication中的coreComponents变量定义并在preInit()函数中被merge到config中来,此处$config[‘components’][‘errorHandler’][‘class’] = yiiwebErrorHandler,
因此registerErrorHandler($config) 最终的结果是执行了ErrorHandler类中的register()方法。
yiiwebErrorHandler的register方法代码:(在yiiaseErrorHandler中定义)
public function register()
{
ini_set('display_errors', false);
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']);
}
该方法主要做了两个动作 set_exception_handler和set_error_handler.
我们先值考虑异常处理:异常处理由ErrorHandler实例的HandleException方法处理。
public function handleException($exception)
{
if ($exception instanceof ExitException) {
return;
}
$this->exception = $exception;
// disable error capturing to avoid recursive errors while handling exceptions
$this->unregister();
// set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
// HTTP exceptions will override this value in renderException()
if (PHP_SAPI !== 'cli') {
http_response_code(500);
}
try {
$this->logException($exception);
if ($this->discardExistingOutput) {
$this->clearOutput();
}
$this->renderException($exception);
if (!YII_ENV_TEST) {
Yii::getLogger()->flush(true);
if (defined('HHVM_VERSION')) {
flush();
}
exit(1);
}
} catch (Exception $e) {
// an other exception could be thrown while displaying the exception
$msg = "An Error occurred while handling another error: ";
$msg .= (string) $e;
$msg .= " Previous exception: ";
$msg .= (string) $exception;
if (YII_DEBUG) {
if (PHP_SAPI === 'cli') {
echo $msg . " ";
} else {
echo '
' . htmlspecialchars($msg, ENT_QUOTES, Yii::$app->charset) . '
';
}
} else {
echo 'An internal server error occurred.';
}
$msg .= " $_SERVER = " . VarDumper::export($_SERVER);
error_log($msg);
if (defined('HHVM_VERSION')) {
flush();
}
exit(1);
}
$this->exception = null;
}
该方法处理完之后,程序退出,可以看到 renderException决定了最终异常的展现形式。renderException是一个纯虚函数,yiiwebErrorHandler里面有默认实现:
protected function renderException($exception)
{
if (Yii::$app->has('response')) {
$response = Yii::$app->getResponse();
// reset parameters of response to avoid interference with partially created response data
// in case the error occurred while sending the response.
$response->isSent = false;
$response->stream = null;
$response->data = null;
$response->content = null;
} else {
$response = new Response();
}
$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;
}
} elseif ($response->format === Response::FORMAT_HTML) {
if (YII_ENV_TEST || isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
// AJAX request
$response->data = '
' . $this->htmlEncode($this->convertExceptionToString($exception)) . '
';
} else {
// if there is an error during error rendering it's useful to
// display PHP error in debug mode instead of a blank screen
if (YII_DEBUG) {
ini_set('display_errors', 1);
}
$file = $useErrorView ? $this->errorView : $this->exceptionView;
$response->data = $this->renderFile($file, [
'exception' => $exception,
]);
}
} elseif ($response->format === Response::FORMAT_RAW) {
$response->data = $exception;
} else {
$response->data = $this->convertExceptionToArray($exception);
}
if ($exception instanceof HttpException) {
$response->setStatusCode($exception->statusCode);
} else {
$response->setStatusCode(500);
}
$response->send();
}
已经不需要再解释什么了,这个就是大家经常看到的异常出错页面的渲染处理过程,如果我们需要定制自己的异常处理方式,需要做的就是继承yiiaseErrorHandler,写一个定制的renderException,最后在$config中定制自己的errorHandler, 如:
‘errorHandler’=>[‘action’=>’site/error’, ‘class’=>’commonwebErrorHandler’]