php的Error與Exception捕獲問題

TDmaggie發表於2017-07-15

最近在專案中前端的php同事發現後臺有個php匯出的指令碼,沒有完成就掛掉了,使用try..catch..方式並沒有捕獲到異常。程式碼是這樣寫的

try {
    $res = $this->doExprot();
} catch (\Exception $e) {
    $msg = '匯出程式異常,被殺死 ******* 錯誤資訊:' . $e->getMessage() . ' ************';
    $res = $e->getTraceAsString();
    $this->threadPool->killThread($this->threadPool->getRedisThreadsKeyPre(), $export_log_id, $msg);
} finally{
    $this->log($supplier_id, $export_log_id, $res);
}

日誌捕獲到的錯誤為:
這裡寫圖片描述

專案使用的php-7.1.2,框架是larval5.3。根據有日誌顯示,但是後續捕獲日誌以及相關邏輯都沒有執行,推斷程式已經意外中斷。到伺服器上ps下pid果然沒有了。那麼為什麼沒有catch到呢。

進入larval框架下檢視Symfony\Component\Debug\Exception\FatalThrowableError原始碼

class FatalThrowableError extends FatalErrorException
{
    public function __construct(\Throwable $e)
    {
        if ($e instanceof \ParseError) {
            $message = 'Parse error: '.$e->getMessage();
            $severity = E_PARSE;
        } elseif ($e instanceof \TypeError) {
            $message = 'Type error: '.$e->getMessage();
            $severity = E_RECOVERABLE_ERROR;
        } else {
            $message = $e->getMessage();
            $severity = E_ERROR;
        }

        \ErrorException::__construct(
            $message,
            $e->getCode(),
            $severity,
            $e->getFile(),
            $e->getLine()
        );

        $this->setTrace($e->getTrace());
    }
}

通過對message的判斷

local.ERROR: Symfony\Component\Debug\Exception\FatalThrowableError: Cannot use object of type stdClass as array in。。。

知道程式定義錯誤級別走的為

$message = $e->getMessage();
$severity = E_ERROR;

在判定程式錯誤級別以及錯誤資訊後,執行ErrorException。but。。這位同事just catch Exception。這就是問題點,解決方式很好處理,只需要增加Error的異常捕獲就可以。

try {
    $res = $this->doExprot();
} catch (\Exception $exception) {

    $msg = '匯出程式異常,被殺死 ******* 錯誤資訊:' . $exception->getMessage() . ' ************';
    $res = $exception->getTraceAsString();
    $this->threadPool->killThread($this->threadPool->getRedisThreadsKeyPre(), $export_log_id, $msg);
} catch(\Error $error) {
    $msg = '匯出程式異常,被殺死 ******* 錯誤資訊:' . $error->getMessage() . ' ************';
    $res = $error->getTraceAsString();
    $this->threadPool->killThread($this->threadPool->getRedisThreadsKeyPre(), $export_log_id, $msg);
}finally{
    $this->log($supplier_id, $export_log_id, $res);
}

Error級別定義

Fatal Error:致命錯誤(指令碼終止執行)
        E_ERROR         // 致命的執行錯誤,錯誤無法恢復,暫停執行指令碼
        E_CORE_ERROR    // PHP啟動時初始化過程中的致命錯誤
        E_COMPILE_ERROR // 編譯時致命性錯,就像由Zend指令碼引擎生成了一個E_ERROR
        E_USER_ERROR    // 自定義錯誤訊息。像用PHP函式trigger_error(錯誤型別設定為:E_USER_ERROR)

    Parse Error:編譯時解析錯誤,語法錯誤(指令碼終止執行)
        E_PARSE  //編譯時的語法解析錯誤

    Warning Error:警告錯誤(僅給出提示資訊,指令碼不終止執行)
        E_WARNING         // 執行時警告 (非致命錯誤)。
        E_CORE_WARNING    // PHP初始化啟動過程中發生的警告 (非致命錯誤) 。
        E_COMPILE_WARNING // 編譯警告
        E_USER_WARNING    // 使用者產生的警告資訊

    Notice Error:通知錯誤(僅給出通知資訊,指令碼不終止執行)
        E_NOTICE      // 執行時通知。表示指令碼遇到可能會表現為錯誤的情況.
        E_USER_NOTICE // 使用者產生的通知資訊。

那麼再php中Exception和Error有什麼區別呢。
在我看來,Error是檢測到的這個問題極有可能使程式無法繼續執行,而Exception則是雖然有問題但是程式繼續執行不受影響。在php7以前的版本中Error型別是不能被捕獲的,僅僅可以捕獲Exception型別。php7以後Error與Exception都繼承了Throwable介面,使得Error被捕獲成為可能。
php官方是這麼介紹Error的

Error is the base class for all internal PHP errors.Since PHP 7 classname "Error" is predefined and used internally.

Error是所有內部php錯誤的基本類,並且自從php7以後Error這個類名,您呀就別用了。。。
其他處理錯誤的方式大家可以看看怎麼運用register_shutdown_function,set_error_handler,set_exception_handler這三個神奇的函式,在php7以下的版本也可以捕獲Error

相關文章