Yii2 之錯誤處理深入分析

外來物種發表於2020-12-07

▪ 前言

在 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 自定義錯誤使用

自定義錯誤的使用詳見 官方教程自定義錯誤顯示

相關文章