(本文基于Yii 1.1版本)
通常,在WebServer中会指定默认访问的入口文件,如 index.php ,在入口文件中会引入框架文件、应用配置等,在最后通过 Yii::createWebApplication($config)->run();
运行。静态方法 Yii::createWebApplication
返回一个 CWebApplication 类的对象,CWebApplication 继承自 CApplication 类,实际执行的是 \CApplication::run
方法。
run() 方法代码如下:
public function run()
{
if($this->hasEventHandler('onBeginRequest'))
$this->onBeginRequest(new CEvent($this));
register_shutdown_function(array($this,'end'),0,false);
$this->processRequest();
if($this->hasEventHandler('onEndRequest'))
$this->onEndRequest(new CEvent($this));
}
在 run() 方法的开始和结尾,分别会产生 “onBeginRequest” 和 “onEndRequest” 事件(通过 \CComponent::raiseEvent
方法),Yii 的框架中利用 Event 机制实现了观察者模式,在运行的不同阶段会触发不同的 Event,用户和框架可以去注册 EventHandler 来监听这些事件并进行相应的处理。
(注册 EventHandler 的方法待补充,两种方式)
真正的请求处理逻辑在 processRequest() 中,该方法是抽象方法,在 CWebApplication 中实现。主要实现的逻辑有:利用 urlManager 组件解析路由,创建相应的 Controller,初始化后执行对应的 Action 。
以上的内容有些跑题,实际上,在 Yii::createWebApplication
创建 CWebApplication 类的对象时,由于 CWebApplication 未定义构造函数,CApplication 的 __construct 方法会被执行,在 initSystemHandlers 方法中,通过 PHP 提供的 set_exception_handler 和 set_error_handler 设置异常处理逻辑和错误处理逻辑。
这段代码如下:
public function __construct($config=null)
{
......
$this->initSystemHandlers();
......
}
protected function initSystemHandlers()
{
if(YII_ENABLE_EXCEPTION_HANDLER)
set_exception_handler(array($this,'handleException'));
if(YII_ENABLE_ERROR_HANDLER)
set_error_handler(array($this,'handleError'),error_reporting());
}
这也就是说,Yii 框架的异常处理底层还是要依赖 PHP 提供的异常处理方法,但是根据 PHP 手册的说明,在PHP 5.X版本下,并非所有错误都可被 set_error_handler 中的处理器所捕获。
根据PHP手册内容,结合开发实际情况总结出下表:
值 | 常量 | 说明 | 能否被捕获 | 举例 |
---|---|---|---|---|
1 | E_ERROR | 致命的运行时错误 | 不能 | Call to a member function on null |
2 | E_WARNING | 运行时警告 | 能 | Illegal string offset |
4 | E_PARSE | 编译时语法解析错误 | 不能 | |
8 | E_NOTICE | 运行时通知 | 能 | Undefined variable |
16 | E_CORE_ERROR | PHP初始化启动中的致命错误 | 不能 | |
32 | E_CORE_WARNING | PHP初始化启动中的警告 | 不能 | |
64 | E_COMPILE_ERROR | 致命编译时错误 | 不能 | |
128 | E_COMPILE_WARNING | 编译时警告 | 不能 | |
256 | E_USER_ERROR | 用户产生的错误信息 | 能 | |
512 | E_USER_WARNING | 用户产生的警告信息 | 能 | |
1024 | E_USER_NOTICE | 用户产生的通知信息 | 能 | |
2048 | E_STRICT | 代码的修改建议 | 能 | |
4096 | E_RECOVERABLE_ERROR | 可被捕捉的致命错误 | 能 | |
8192 | E_DEPRECATED | 运行时兼容性通知 | 能 | 使用 $HTTP_RAW_POST_DATA 变量 |
16384 | E_USER_DEPRECATED | 用户产生的警告信息 | 能 | |
30719 | E_ALL | E_STRICT外的所有信息 | 能 |
PHP手册链接:
http://php.net/manual/zh/function.set-error-handler.php
http://php.net/manual/zh/errorfunc.constants.php
能被 handleError 捕获到的错误会进入 CApplication::handleError 方法中,依次经过 3 段处理逻辑
1、日志记录
这是 Yii 框架默认的日志记录逻辑,下载 Yii 框架运行 Demo 时,如果产生错误,就调用到这部分代码,在 application.log 中生成记录。
$log="$message ($file:$line)\nStack trace:\n";
$trace=debug_backtrace();
// skip the first 3 stacks as they do not tell the error position
if(count($trace)>3)
$trace=array_slice($trace,3);
foreach($trace as $i=>$t)
{
if(!isset($t['file']))
$t['file']='unknown';
if(!isset($t['line']))
$t['line']=0;
if(!isset($t['function']))
$t['function']='unknown';
$log.="#$i {$t['file']}({$t['line']}): ";
if(isset($t['object']) && is_object($t['object']))
$log.=get_class($t['object']).'->';
$log.="{$t['function']}()\n";
}
if(isset($_SERVER['REQUEST_URI']))
$log.='REQUEST_URI='.$_SERVER['REQUEST_URI'];
Yii::log($log,CLogger::LEVEL_ERROR,'php');
2、产生一个 OnError 事件
OnError事件由以下代码产生:
$event=new CErrorEvent($this,$code,$message,$file,$line);
$this->onError($event);
根据上文说到的 EventHandler 机制,如果注册了错误处理方法,那么该方法会被框架调用。在 Demo-blog 中的 main.php 中,可以看到以下代码:
'errorHandler'=>array(
// use 'site/error' action to display errors
'errorAction'=>'site/error',
),
所以发生(可被 set_error_handler 捕获的)错误时,SiteController::actionError 方法会被调用
3、显示一个错误页面
发生错误时,通常会看到这样的页面
通过以下这部分代码实现:
$event=new CErrorEvent($this,$code,$message,$file,$line);
$this->onError($event);
if(!$event->handled)
{
// try an error handler
if(($handler=$this->getErrorHandler())!==null)
$handler->handle($event);
else
$this->displayError($code,$message,$file,$line);
}
如果注册了额外的处理器,那么进入 handle() 方法,先利用这些处理器处理错误,最后进入 CErrorHandler::handleError 方法中,将 HTTP 响应码设置为 500,返回带有错误信息的页面。(注意最后这里会判断 YII_DEBUG 的值,避免在生产环境中显示完整错误信息)
如果未注册错误处理器,直接进入 CApplication::displayError 方法显示错误页面。
从这段逻辑能看到,即使 PHP 发生轻微错误(E_NOTICE 级别),也同样会被 Yii 框架认为是错误而终止运行,直接给出 500 的响应码,所以这点较为严格。然而有时可能出于实际业务逻辑,能够容忍 E_NOTICE 错误,可以通过修改框架代码解决。即在 CApplication::handleError 中判断 code 值,如果为 E_NOTICE,则直接 return 不需要进行错误处理,从而使业务逻辑继续执行。
另外还有一种方式,上面的前两行代码产生了 onError 事件,如果在 onError 事件的监听器里将 CErrorEvent::handled 属性设置为 true,那么就不会调用配置文件中配置的 errorHandler 处理方法。CApplication::onError 方法的注释是这样写的:
Raised when a PHP execution error occurs.
An event handler can set the {@link CErrorEvent::handled handled}
property of the event parameter to be true to indicate no further error
handling is needed. Otherwise, the {@link getErrorHandler errorHandler}
application component will continue processing the error.
这样只需要在 onError 事件的监听器中根据 $event->code
,把想忽略掉的错误设置 $event->handled=true
属性即可。在配置文件最前面(如自带的 Demo 项目的 main.php 文件的 return array 之前)增加代码如下:
Yii::app()->attachEventHandler('onError', function ($event) {
if ($event->code === E_NOTICE) {
// 按需将 $event 信息记录到日志 // Yii::log()......
$event->handled=true;
}
});
这样不需要修改框架内部逻辑,是比较推荐的一种方式。