你知道PHP中Exception, Error Handler的這些細

wjaning發表於2021-09-09

前言

最近專案中有一個功能需要實現:
除錯模式下, 將所有錯誤提前輸出, 再輸出頁面內容.

為實現上述功能, 需使用到Exception, Error相關Handler方法, 發現有許多坑, 故寫此文與大家分享.

主要函式

此篇文章重點關注以下幾個函式

  • error_reporting()

  • set_error_handler()

  • set_exception_handler()

  • register_shutdown_function()

  • error_get_last()

這有什麼難的?

哈~ 如果您現在有標題中的感慨, 那麼也請關注以下本文中將重點講述的問題列表:

  1. error_reporting()error_get_last() 有什麼聯絡?

  2. set_error_handler()set_exception_handler() 繫結的handler什麼時候才會啟動?  它們有什麼聯絡?

  3. register_shutdown_function()通常跟Exception/Error有關係麼?

上述問題描述模糊, 因此答案也可能千人千面.
因此, 本文只給出自己的答案與大家分享, 如有問題或不同的見解, 期待與您溝通.
如果以上問題, 並不能引起您的興趣, 或者您已理解透徹了, 就可以自行右上角小紅叉啦~

解疑:

1. error_reporting()error_get_last() 有什麼聯絡?


  • int error_reporting ([ int $level ] )
    大家應該再熟悉不過了, 因此不再贅述.

  • array error_get_last ( void )

    獲取最後發生的錯誤.

    通常用來獲取PHP執行過程中的Fatal Error錯誤(PHP 5).

這兩個函式在字面上關聯性並不強, 但請觀察以下程式碼及輸出

 8
    [message] => Undefined variable: b
    [file] => /app/t.php
    [line] => 3
)
*/

error_get_last()雖然說明了獲取最後發生的錯誤, 實際上也是如此. 但卻沒有說明, 被error_reporting()忽略掉的錯誤是否有可能被獲取到, 因此, 當我們使用error_get_last()時需要注意我平時忽略掉的錯誤, 如: E_DEPRECATED

2. set_error_handler()set_exception_handler() 繫結的handler什麼時候才會啟動? 它們有什麼聯絡?


  • mixed set_error_handler ( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] )

    設定使用者自定義的錯誤處理函式.

    通常在PHP指令碼執行過程中, 出現一些非中斷性錯誤時觸發.
    我們會用這個來記錄錯誤日誌或直接輸出等操作.
    注意:

    • FALSE: 標準的錯誤處理依然會被執行(標準錯誤處理根據 display_errors = true/false 決定是否輸出到stderr)

  1. 引數$error_types大多設定為error_reporting(), 但建議設定為E_ALL, 具體哪些錯誤需要被處理, 哪些不需要, 在handler內進行判斷明顯更加靈活.

  2. 以下級別的錯誤不能由使用者定義的函式來處理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在 呼叫 set_error_handler() 函式所在檔案中產生的大多數 E_STRICT

  3. handler被觸發後, 並不會中斷PHP執行.

  4. bool error_handler ( int $errno , string $errstr [, string $errfile [, int $errline [, array $errcontext ]]] )
    注意error_handler的返回值:

callable set_exception_handler ( callable $exception_handler )

設定使用者自定義的異常處理函式
設定預設的異常處理程式,用於沒有用 try/catch 塊來捕獲的異常。 在 exception_handler 呼叫後異常會中止。

注意:

注意點中2, 3項輕描淡寫了一下PHP 5/PHP 7之間的不同卻透露出重要的訊息(坑..)
PHP 7中, exception_handler 不再只接受Exception了, 並且接收了Error錯誤.

  1. exception_handler 呼叫後異常會中止(指令碼終止).

  2. PHP 5, PHP 7exception_handler並不相同.
    PHP 5: void handler ( Exception $ex )
    PHP 7: void handler ( Throwable $ex )

  3. 自 PHP 7 以來,大多數錯誤丟擲 Error 異常,也能被捕獲。 Error 和 Exception 都實現了 Throwable 介面。

因此, set_error_handler()set_exception_handler() 之間的關係也迎刃而解:

  • PHP 5:

    • set_error_handler(): 負責非中斷行錯誤.

    • set_exception_handler(): 負責沒有被catch的異常(會中斷).

    • Fatal Error等: 並不會被兩者管理, 正常輸出到螢幕上(弊端).

  • PHP 7:

    • set_error_handler(): 負責非中斷行錯誤.

    • set_exception_handler(): 負責沒有被catch的異常, Error(會中斷)

    • Fatal Error等: 由set_exception_handler()管理.

3. register_shutdown_function()通常跟Exception/Error有關係麼?

註冊一個 callback ,它會在指令碼執行完成或者 exit() 後被呼叫。

根據說明可以得出結論, 它與Exception/Error完全沒關係.
提出這個問題, 主要是因為, 在PHP5Fatal Error並沒有明確的接收地點, 所以我們通常配合error_get_last()來接收Fatal Error

 output:
Array
(
    [type] => 1
    [message] => Uncaught Error: Call to undefined function unknown_function() in /app/t.php:3
Stack trace:#0 {main}
  thrown
    [file] => /app/t.php
    [line] => 3
)
*/

然而隨著PHP 7的到來, Error已經可以被set_exception_handler()捕捉了, 再透過error_get_last()就多餘了. shutdown中更多的是一些版本冗餘的工作.

栗子

前言中的需求: 除錯模式下, 將所有錯誤提前輸出, 再輸出頁面內容.
以下是demo, 省去了環境判斷(debug環境), 大家可以根據下面這段程式碼, 瞭解本文中所說的各種handler的觸發和呼叫情況.

要求: 將所有異常列印在螢幕最上方
*//* Fatal Error 中斷指令碼 -> shutdown_handler *///設定錯誤級別define("END_ERRORS", '--END ERRORS--' . PHP_EOL . PHP_EOL);
ini_set('display_errors', true);
ini_set('error_reporting', E_ALL & ~E_DEPRECATED);

set_error_handler('usr_err_handler', error_reporting()); //註冊錯誤處理函式set_exception_handler('usr_ex_handler'); //註冊異常處理函式register_shutdown_function('shutdown_handler');    //註冊會在php中止時執行的函式$global_errors = [];    //用於記錄所有錯誤$errnos = [             //錯誤級別
    0 => 'ERROR',//PHP7 ERROR的CODE
    1 => 'E_ERROR',//FATAL ERROR(PHP5), E_ERROR
    2 => 'E_WARNING',    4 => 'E_PARSE',    8 => 'E_NOTICE',    16 => 'E_CORE_ERROR',    32 => 'E_CORE_WARNING',    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',    16384 => 'E_USER_DEPRECATED',    30719 => 'E_ALL',
];function reset_errors(){    global $global_errors;
    $global_errors = [];
}function get_errnostr($errno){    global $errnos;    return $errnos[$errno];
}function set_errnos($errno, $errstr){    global $global_errors;
    $global_errors[] = [        'errno' => $errno,        'errnostr' => get_errnostr($errno),        'errstr' => $errstr,
    ];
}function print_errors($prefix){    global $global_errors;    foreach ($global_errors as $err) {//由於handler中依然有可能有error 因此放最後
        printf("[%s]: %s, %d, %sn",
            $prefix, $err['errnostr'], $err['errno'], $err['errstr']);
    }
}//使用者異常處理函式 (進來就中斷指令碼) PHP5只有Exception進來   PHP7Error和Exception//PHP7中 void handler (Throwable $ex) 可捕獲Error和Exception兩種異常, 暫不管// PHP7 Error閱讀//內部如果有Error則觸發Error函式, 再回到錯誤行繼續執行function usr_ex_handler($ex){
    $content = ob_get_clean();  //讓Exception/Error提前展示

    print_errors('EX ERROR');
    reset_errors();

    $errnostr = get_errnostr($ex->getCode());
    $errno = $ex->getCode();
    $errstr = $ex->getMessage();    if ($ex instanceof Exception) {
        printf("[EXCEPTION]: %s, %d, %sn", $errnostr, $errno, $errstr);
    } else {//針對PHP7  $ex instanceof Error
        printf("[EX FATAL ERROR]: %s, %d, %sn", $errnostr, $errno, $errstr);
    }    //由於handler中依然有可能有error 因此放最後
    print_errors('EX ERROR');
    reset_errors();    echo END_ERRORS;    echo $content;    return;
}//使用者錯誤處理函式//E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING不能被使用者處理function usr_err_handler($errno, $errstr, $errfile, $errline, $errcontext){
    set_errnos($errno, $errstr);    return true;    //如果函式返回 FALSE,標準錯誤處理處理程式將會繼續呼叫。}//使用者PHP終止函式function shutdown_handler(){
    $content = ob_get_clean();  //讓Exception/Error提前展示
    $err = error_get_last();//檢查一下是否有遺漏掉的錯誤 php5 fatal error
    if ($err['type'] & error_reporting()) {
        set_errnos($err['type'], $err['message']);
    }
    print_errors('ST ERROR');
    reset_errors();    echo $content;
}

ob_start();echo 'Main function...', PHP_EOL;//搞事情//throw new Exception('這是一個異常');trigger_error('這是一個使用者error');//E_USER_NOTICEif (version_compare(PHP_VERSION, '7.0.0') >= 0) {
    mcrypt_encrypt();//E_WARNING, E_DEPRECATED} else {
    mysql();
}
unknown_function(); //fatal error$content = ob_get_clean();//優先輸出錯誤print_errors('MA ERROR');if (!empty($global_errors)) {    echo END_ERRORS;
}
reset_errors();//輸出正文內容echo $content;



作者:鋼管君
連結:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2983/viewspace-2803835/,如需轉載,請註明出處,否則將追究法律責任。

相關文章