這篇文章主要對php中的錯誤處理進行簡單的記錄
php一開始被設計為一門面向過程的語言,所以異常處理沒有使用像Java一樣的 try / catch 機制,出錯時直接顯示到頁面上,或者記錄到web服務器的錯誤日志中,并且php的錯誤分成了很多的級別,例如E_ERROR、E_WARNING、E_PARSE、E_NOTICE等等,對于像E_ERROR、E_PARSE這樣的嚴重錯誤,php會直接終止腳本的運行。線下開發(fā)時,我們一般會設置error_reporting(-1)和ini_set('display_errors', 'on')來報告并顯示所有異常,但是線上運行的項目,出于安全的考慮,我們一般會隱藏錯誤日志的頁面輸出,如果php的配置文件中開啟了log_error,那么相關(guān)的錯誤日志可以在PHP的錯誤日志中查詢,但是如果我們是多項目呢,通過PHP錯誤日志來排錯,顯然不科學,那么我們?nèi)绾蜗窨蚣苤幸粯觼韮?yōu)雅的捕捉異常呢?相信有一定研發(fā)經(jīng)驗的同學,一定聽說過或者用過大名鼎鼎的laravel框架,那么它是如何處理異常的呢?通過閱讀源碼,有興趣可以在Illuminate\Foundation\Bootstrap\HandleExceptions.php查看具體實現(xiàn)。這里只對其中的核心部分進行分享,其中主要有三個PHP內(nèi)置函數(shù)的調(diào)用:set_error_handler、set_exception_handler和register_shutdown_function
set_error_handler函數(shù)
set_error_handler來注冊自己的錯誤處理方法來代替php的標準錯誤處理方式(輸出到頁面或者記錄到日志),但是一些嚴重錯誤是無法通過這種方式來處理的,具體我們來看手冊對該方法的介紹
mixed set_error_handler ( callable
error_types = E_ALL | E_STRICT ] )
設置一個用戶的函數(shù)(error_handler)來處理腳本中出現(xiàn)的錯誤。
本函數(shù)可以用你自己定義的方式來處理運行中的錯誤, 例如,在應用程序中嚴重錯誤發(fā)生時,或者在特定條件下觸發(fā)了一個錯誤(使用 trigger_error()),你需要對數(shù)據(jù)/文件做清理回收。
重要的是要記住 error_types 里指定的錯誤類型都會繞過 PHP 標準錯誤處理程序, 除非回調(diào)函數(shù)返回了 FALSE。 error_reporting() 設置將不會起到作用而你的錯誤處理函數(shù)繼續(xù)會被調(diào)用 —— 不過你仍然可以獲取 error_reporting 的當前值,并做適當處理。 需要特別注意的是帶 @ error-control operator 前綴的語句發(fā)生錯誤時,這個值會是 0。
同時注意,在需要時你有責任使用 die()。 如果錯誤處理程序返回了,腳本將會繼續(xù)執(zhí)行發(fā)生錯誤的后一行。
以下級別的錯誤不能由用戶定義的函數(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。
set_exception_handler函數(shù)
set_exception_handler注冊異常處理函數(shù),這樣當有未被catch的異常產(chǎn)生時,系統(tǒng)會為我們自動調(diào)用注冊的處理函數(shù)來處理。相關(guān)詳細描述請查看官網(wǎng)文檔set_exception_handler函數(shù)文檔
register_shutdown_function函數(shù)
register_shutdown_function注冊一個會在php中止時執(zhí)行的函數(shù)注冊一個 callback ,它會在腳本執(zhí)行完成或者 exit()后被調(diào)用。關(guān)詳細描述請查看官網(wǎng)文檔register_shutdown_function函數(shù)文檔
最后直接上干貨
<?php
class myErrorException
{
private $fileRootName;
private $fileName;
public function __construct(string $fileName = '/tmp/php/', bool $bugType = false)
{
$this->fileRootName = $fileName . date('Y-m-d') . '.log';
if (!$bugType) {
$this->checkDir();
$this->errorHandle();
}
}
private function checkDir(){
$this->fileName = $this->fileRootName . date('Y-m-d') . '.log';
if (!file_exists($this->fileName) && !is_dir($this->fileRootName)) {
@mkdir($this->fileRootName, '0777', true);
}
}
/**
* 注冊全局的錯誤處理函數(shù)
*/
private function errorHandle()
{
// 注冊自己的錯誤處理方法來代替php的標準錯誤處理方式(輸出到頁面或者記錄到日志),但是一些嚴重錯誤是無法通過這種方式來處理的
set_error_handler([$this, '_handleError']);
// 注冊異常處理函數(shù),這樣當有未被catch的異常產(chǎn)生時,系統(tǒng)會為我們自動調(diào)用注冊的處理函數(shù)來處理。
set_exception_handler([$this, '_handleException']);
// 注冊捕捉致命錯誤
register_shutdown_function([$this, '_handleShutdown']);
}
/**
* 錯誤處理
* @param int $level
* @param string $message
* @param string $file
* @param int $line
* @param array $context
*/
public function _handleError(int $level, string $message, string $file = '', int $line = 0, array $context = [])
{
if (error_reporting() & $level) {
$this->_logHandle($message, $level, $file, $line);
}
}
/**
* 未捕捉的異常
* @param \Throwable $e
*/
public function _handleException(\Throwable $e)
{
if (!$e instanceof \Error) {
$this->_logHandle($e->getMessage(), $e->getCode(), $e->getFile(), $e->getLine());
} else {
$this->_logHandle($e->getMessage(), $e->getCode(), $e->getFile(), $e->getLine());
}
}
/**
* 致命錯誤
*/
public function _handleShutdown()
{
$error = error_get_last();
if (!is_null($error)) {
$this->_logHandle($error['message'], $error['type'], $error['file'], $error['line']);
}
}
/**
* 日志記錄格式
* @param $message
* @param $level
* @param $file
* @param $line
*/
private function _logHandle($message, $level, $file, $line)
{
$logData = [
'message' => $message,
'level' => $level,
'file' => $file,
'line' => $line
];
$logStr = date('Y-m-d H:i:s') . " error => " . json_encode($logData);
file_put_contents($this->fileRootName, json_encode($logStr));
throw new \Error($message, 500);
}
}
$logObj = new myErrorException();
new testBugA();
die('test');
此時上面的PHP代碼網(wǎng)頁中顯示:

然后服務器日志中顯示:

du -h / --max-depth=2 | sort -nr | head -5