Yii2 之错误处理深入分析

▪ 前言

在 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

下面的函数主要是设置错误处理的默认类,如果你的配置文件中没有设置 errorHandlerclass,那么将直接使用 '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 类对象的 handleExceptionhandleError 方法。

其实 yii\web\ErrorHandler 类中并没有 handleExceptionhandleError 方法,但是在其基类 yii\base\ErrorHandler 有,这两个函数最终都将调用 yii\web\ErrorHandlerrenderException 方法,代码如下:

# 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 我们可以看出:自定义的错误需特定的情况下才能生效

▪ Yii2 自定义错误使用

自定义错误的使用详见 官方教程自定义错误显示

上一篇:Vue: Uncaught TypeError: Cannot set property 'errorHandler' of undefined


下一篇:MFC 编辑框 显示 滚动到底部