原始碼分析
錯誤及異常處理機制
錯誤及異常處理機制檔案是/thinkphp/library/think/Error.php,在框架引導檔案的的基礎檔案base.php中註冊(不知道的可以去看《《原始碼分析(二)—入口篇》》),通過thinkError::register()進行的註冊。
/**
* 註冊異常處理
* @access public
* @return void
*/
public static function register()
{
error_reporting(E_ALL);
set_error_handler([__CLASS__, 'appError']);
set_exception_handler([__CLASS__, 'appException']);
register_shutdown_function([__CLASS__, 'appShutdown']);
}
該方法做了四件事情:
- 設定報錯級別 E_ALL為E_STRICT所有報錯。
- 設定錯誤處理函式,set_error_handler([__CLASS__, 'appError'])
- 設定異常處理函式,set_exception_handler([__CLASS__, 'appException']);
- 設定程式異常終止處理函式,register_shutdown_function([__CLASS__, 'appShutdown']);
PHP報錯級別
php的報錯級別有:E_STRICT,E_ALL, E_USER_WARNING等,具體可檢視[php
預定義常量]。
錯誤處理函式
thinkphp中註冊了thinkError::appError()方法對錯誤進行處理。
/**
* 錯誤處理
* @access public
* @param integer $errno 錯誤編號
* @param integer $errstr 詳細錯誤資訊
* @param string $errfile 出錯的檔案
* @param integer $errline 出錯行號
* @return void
* @throws ErrorException
*/
public static function appError($errno, $errstr, $errfile = '', $errline = 0)
{
$exception = new ErrorException($errno, $errstr, $errfile, $errline);
// 符合異常處理的則將錯誤資訊託管至 think\exception\ErrorException
if (error_reporting() & $errno) {
throw $exception;
}
self::getExceptionHandler()->report($exception);
}
在appError方法中,把符合異常處理的則將錯誤資訊託管至系統的ErrorException,其他的異常通過thinkexceptionHandle進行處理。
//think\exception\ErrorException檔案
/**
* ThinkPHP錯誤異常
* 主要用於封裝 set_error_handler 和 register_shutdown_function 得到的錯誤
* 除開從 think\Exception 繼承的功能
* 其他和PHP系統\ErrorException功能基本一樣
*/
class ErrorException extends Exception
{
/**
* 用於儲存錯誤級別
* @var integer
*/
protected $severity;
/**
* 錯誤異常建構函式
* @param integer $severity 錯誤級別
* @param string $message 錯誤詳細資訊
* @param string $file 出錯檔案路徑
* @param integer $line 出錯行號
* @param array $context 錯誤上下文,會包含錯誤觸發處作用域內所有變數的陣列
*/
public function __construct($severity, $message, $file, $line, array $context = [])
{
$this->severity = $severity;
$this->message = $message;
$this->file = $file;
$this->line = $line;
$this->code = 0;
empty($context) || $this->setData('Error Context', $context);
}
/**
* 獲取錯誤級別
* @return integer 錯誤級別
*/
final public function getSeverity()
{
return $this->severity;
}
}
errorException設定錯誤級別,錯誤資訊,出錯檔案路徑,行號,上下文。
對exception進行處理的是thinkexceptionHandle的report()方法:self::getExceptionHandler()->report($exception);
//self::getExceptionHandler()
/**
* 獲取異常處理的例項
* @access public
* @return Handle
*/
public static function getExceptionHandler()
{
static $handle;
if (!$handle) {
// 異常處理 handle
$class = Config::get('exception_handle');
if ($class && is_string($class) && class_exists($class) &&
is_subclass_of($class, "\\think\\exception\\Handle")
) {
$handle = new $class;
} else {
$handle = new Handle;
if ($class instanceof \Closure) {
$handle->setRender($class);
}
}
}
return $handle;
}
這裡有一個關鍵的地方是:static $handle; 宣告該變數是靜態變數時候,當賦值給該變數後,函式呼叫結束後不會銷燬,直到指令碼結束才會銷燬。
這個邏輯就是判斷$handle是否已經賦值,沒有賦值,獲取預設配置檔案是否設定處理handle,如果設定,這個handle必須是\think\exception\Handle的子類(is_subclass_of($class, "\think\exception\Handle")),如果沒有設定,那麼用預設的thinkexceptionHandle呼叫report方法進行處理, 記錄到日誌檔案中。
/**
* Report or log an exception.
*
* @param \Exception $exception
* @return void
*/
public function report(Exception $exception)
{
if (!$this->isIgnoreReport($exception)) {
// 收集異常資料
if (App::$debug) {
$data = [
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'message' => $this->getMessage($exception),
'code' => $this->getCode($exception),
];
$log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]";
} else {
$data = [
'code' => $this->getCode($exception),
'message' => $this->getMessage($exception),
];
$log = "[{$data['code']}]{$data['message']}";
}
if (Config::get('record_trace')) {
$log .= "\r\n" . $exception->getTraceAsString();
}
Log::record($log, 'error');
}
}
把errorException的資料組裝成對應的字串,寫入日誌。
異常處理函式
thinkphp中註冊了thinkError::appException()方法對錯誤進行處理。
/**
* 異常處理
* @access public
* @param \Exception|\Throwable $e 異常
* @return void
*/
public static function appException($e)
{
if (!$e instanceof \Exception) {
$e = new ThrowableError($e);
}
$handler = self::getExceptionHandler();
$handler->report($e);
if (IS_CLI) {
$handler->renderForConsole(new ConsoleOutput, $e);
} else {
$handler->render($e)->send();
}
}
方法和appError處理差不多,基本都是通過獲取ExceptionHandle再呼叫handle的report方法,但是多了一步把異常呈現,如果是命令列寫到命令列輸出,如果是web的就把錯誤資訊通過reponse響應返回客戶端。
異常中止時執行的函式
thinkphp中註冊了thinkError::appShutdown()方法對錯誤進行處理。
/**
* 異常中止處理
* @access public
* @return void
*/
public static function appShutdown()
{
// 將錯誤資訊託管至 think\ErrorException
if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) {
self::appException(new ErrorException(
$error['type'], $error['message'], $error['file'], $error['line']
));
}
// 寫入日誌
Log::save();
}
通過error_get_last()獲取最後丟擲的錯誤,把資訊託管至thinkErrorException,在通過異常處理函式進行記錄資訊。最後寫入日誌。
總結
整體整個錯誤處理機制都是通過獲取ExceptionHandle再呼叫handle的report方法,但是多了一步把異常呈現,如果是命令列寫到命令列輸出,如果是web的就把錯誤資訊通過reponse響應返回客戶端。預設的處理handle是thinkexceptionHandle,當然也可以自定義handle,但是必須是thinkexceptionHandle的子類, 通過self::getExceptionHandler的is_subclass_of($class, "\think\exception\Handle")可以知。