Yii框架源码阅读:错误处理与日志打印

本文详细探讨了Yii框架的错误处理和日志打印机制,从`run()`方法开始,阐述了如何通过`set_exception_handler`和`set_error_handler`设置异常和错误处理器。Yii框架会在错误发生时记录日志,触发`OnError`事件,以及显示错误页面。通过调整框架代码或监听`onError`事件,开发者可以选择性地忽略某些错误,确保生产环境的稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(本文基于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_handlerset_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手册内容,结合开发实际情况总结出下表:

常量说明能否被捕获举例
1E_ERROR致命的运行时错误不能Call to a member function on null
2E_WARNING运行时警告Illegal string offset
4E_PARSE编译时语法解析错误不能
8E_NOTICE运行时通知Undefined variable
16E_CORE_ERRORPHP初始化启动中的致命错误不能
32E_CORE_WARNINGPHP初始化启动中的警告不能
64E_COMPILE_ERROR致命编译时错误不能
128E_COMPILE_WARNING编译时警告不能
256E_USER_ERROR用户产生的错误信息
512E_USER_WARNING用户产生的警告信息
1024E_USER_NOTICE用户产生的通知信息
2048E_STRICT代码的修改建议
4096E_RECOVERABLE_ERROR可被捕捉的致命错误
8192E_DEPRECATED运行时兼容性通知使用 $HTTP_RAW_POST_DATA 变量
16384E_USER_DEPRECATED用户产生的警告信息
30719E_ALLE_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;
    }
});

这样不需要修改框架内部逻辑,是比较推荐的一种方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值