前言
最近項目中有一個功能需要實現(xiàn):
調(diào)試模式下, 將所有錯誤提前輸出, 再輸出頁面內(nèi)容.
為實現(xiàn)上述功能, 需使用到Exception
, Error
相關(guān)Handler
方法, 發(fā)現(xiàn)有許多坑, 故寫此文與大家分享.
主要函數(shù)
此篇文章重點關(guān)注以下幾個函數(shù)
error_reporting()
set_error_handler()
set_exception_handler()
register_shutdown_function()
error_get_last()
這有什么難的?
哈~ 如果您現(xiàn)在有標(biāo)題中的感慨, 那么也請關(guān)注以下本文中將重點講述的問題列表:
-
error_reporting()
與error_get_last()
有什么聯(lián)系? -
set_error_handler()
與set_exception_handler()
綁定的handler
什么時候才會啟動? 它們有什么聯(lián)系? -
register_shutdown_function()
通常跟Exception/Error
有關(guān)系么?
上述問題描述模糊, 因此答案也可能千人千面.
因此, 本文只給出自己的答案與大家分享, 如有問題或不同的見解, 期待與您溝通.
如果以上問題, 并不能引起您的興趣, 或者您已理解透徹了, 就可以自行右上角小紅叉啦~
解疑:
1. error_reporting()
與 error_get_last()
有什么聯(lián)系?
link: php.net - error_reporting()
link: php.net - error_get_last()
int error_reporting ([ int $level ] )
大家應(yīng)該再熟悉不過了, 因此不再贅述.-
array error_get_last ( void )
獲取最后發(fā)生的錯誤.
通常用來獲取PHP運行過程中的
Fatal Error
錯誤(PHP 5
).
這兩個函數(shù)在字面上關(guān)聯(lián)性并不強, 但請觀察以下代碼及輸出
<?php
error_reporting(E_ALL & ~E_NOTICE);
$a = $b; //E_NOTICE
print_r(error_get_last());
/* output:
Array
(
[type] => 8
[message] => Undefined variable: b
[file] => /app/t.php
[line] => 3
)
*/
error_get_last()
雖然說明了獲取最后發(fā)生的錯誤, 實際上也是如此. 但卻沒有說明, 被error_reporting()
忽略掉的錯誤是否有可能被獲取到, 因此, 當(dāng)我們使用error_get_last()
時需要注意我平時忽略掉的錯誤, 如: E_DEPRECATED
2. set_error_handler()
與 set_exception_handler()
綁定的handler
什么時候才會啟動? 它們有什么聯(lián)系?
link: php.net - set_error_handler()
link: php.net - set_exception_handler()
-
mixed set_error_handler ( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] )
設(shè)置用戶自定義的錯誤處理函數(shù).
通常在PHP腳本運行過程中, 出現(xiàn)一些非中斷性錯誤時觸發(fā).
我們會用這個來記錄錯誤日志或直接輸出等操作.
注意:參數(shù)
$error_types
大多設(shè)定為error_reporting()
, 但建議設(shè)定為E_ALL
, 具體哪些錯誤需要被處理, 哪些不需要, 在handler
內(nèi)進行判斷明顯更加靈活.以下級別的錯誤不能由用戶定義的函數(shù)來處理: E_ERROR丁存、 E_PARSE、 E_CORE_ERROR辽聊、 E_CORE_WARNING痴脾、 E_COMPILE_ERROR颤介、 E_COMPILE_WARNING,和在 調(diào)用 set_error_handler() 函數(shù)所在文件中產(chǎn)生的大多數(shù) E_STRICT
handler
被觸發(fā)后, 并不會中斷PHP運行.-
bool error_handler ( int $errno , string $errstr [, string $errfile [, int $errline [, array $errcontext ]]] )
注意error_handler
的返回值:-
FALSE
: 標(biāo)準(zhǔn)的錯誤處理依然會被執(zhí)行(標(biāo)準(zhǔn)錯誤處理根據(jù) display_errors = true/false 決定是否輸出到stderr
)
-
-
callable set_exception_handler ( callable $exception_handler )
設(shè)置用戶自定義的異常處理函數(shù)
設(shè)置默認(rèn)的異常處理程序赞赖,用于沒有用 try/catch 塊來捕獲的異常滚朵。 在 exception_handler 調(diào)用后異常會中止。注意:
-
exception_handler
調(diào)用后異常會中止(腳本終止). -
PHP 5
,PHP 7
的exception_handler
并不相同.
PHP 5
:void handler ( Exception $ex )
PHP 7
:void handler ( Throwable $ex )
自 PHP 7 以來前域,大多數(shù)錯誤拋出 Error 異常辕近,也能被捕獲。 Error 和 Exception 都實現(xiàn)了 Throwable 接口匿垄。
注意點中2, 3項輕描淡寫了一下
PHP 5/PHP 7
之間的不同卻透露出重要的消息(坑..)
PHP 7
中,exception_handler
不再只接受Exception
了, 并且接收了Error
錯誤.
link: php.net - PHP7 Errors列表 -
因此, set_error_handler()
與 set_exception_handler()
之間的關(guān)系也迎刃而解:
-
PHP 5
:-
set_error_handler()
: 負(fù)責(zé)非中斷行錯誤. -
set_exception_handler()
: 負(fù)責(zé)沒有被catch的異常(會中斷). -
Fatal Error
等: 并不會被兩者管理, 正常輸出到屏幕上(弊端).
-
-
PHP 7
:-
set_error_handler()
: 負(fù)責(zé)非中斷行錯誤. -
set_exception_handler()
: 負(fù)責(zé)沒有被catch的異常,Error
(會中斷) -
Fatal Error
等: 由set_exception_handler()
管理.
-
3. register_shutdown_function()
通常跟Exception/Error有關(guān)系么?
link: php.net - register_shutdown_function()
注冊一個 callback 移宅,它會在腳本執(zhí)行完成或者 exit() 后被調(diào)用。
根據(jù)說明可以得出結(jié)論, 它與Exception/Error
完全沒關(guān)系.
提出這個問題, 主要是因為, 在PHP5
中Fatal Error
并沒有明確的接收地點, 所以我們通常配合error_get_last()
來接收Fatal Error
<?php
register_shutdown_function('shutdown_function');
unknown_function();
function shutdown_function() {
print_r(error_get_last());
}
/* 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
已經(jīng)可以被set_exception_handler()
捕捉了, 再通過error_get_last()
就多余了. shutdown
中更多的是一些版本冗余的工作.
栗子
前言中的需求: 調(diào)試模式下, 將所有錯誤提前輸出, 再輸出頁面內(nèi)容.
以下是demo, 省去了環(huán)境判斷(debug環(huán)境), 大家可以根據(jù)下面這段代碼, 了解本文中所說的各種handler
的觸發(fā)和調(diào)用情況.
<?php
/*
要求: 將所有異常打印在屏幕最上方
*/
/* Fatal Error 中斷腳本 -> shutdown_handler */
//設(shè)置錯誤級別
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()); //注冊錯誤處理函數(shù)
set_exception_handler('usr_ex_handler'); //注冊異常處理函數(shù)
register_shutdown_function('shutdown_handler'); //注冊會在php中止時執(zhí)行的函數(shù)
$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, %s\n",
$prefix, $err['errnostr'], $err['errno'], $err['errstr']);
}
}
//用戶異常處理函數(shù) (進來就中斷腳本) PHP5只有Exception進來 PHP7Error和Exception
//PHP7中 void handler (Throwable $ex) 可捕獲Error和Exception兩種異常, 暫不管
//http://php.net/manual/en/language.errors.php7.php PHP7 Error閱讀
//內(nèi)部如果有Error則觸發(fā)Error函數(shù), 再回到錯誤行繼續(xù)執(zhí)行
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, %s\n", $errnostr, $errno, $errstr);
} else {//針對PHP7 $ex instanceof Error
printf("[EX FATAL ERROR]: %s, %d, %s\n", $errnostr, $errno, $errstr);
}
//由于handler中依然有可能有error 因此放最后
print_errors('EX ERROR');
reset_errors();
echo END_ERRORS;
echo $content;
return;
}
//用戶錯誤處理函數(shù)
//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; //如果函數(shù)返回 FALSE椿疗,標(biāo)準(zhǔn)錯誤處理處理程序?qū)^續(xù)調(diào)用漏峰。
}
//用戶PHP終止函數(shù)
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_NOTICE
if (version_compare(PHP_VERSION, '7.0.0') >= 0) {
mcrypt_encrypt();//E_WARNING, E_DEPRECATED
} else {
mysql();
}
unknown_function(); //fatal error
$content = ob_get_clean();
//優(yōu)先輸出錯誤
print_errors('MA ERROR');
if (!empty($global_errors)) {
echo END_ERRORS;
}
reset_errors();
//輸出正文內(nèi)容
echo $content;