- 寫在前面:tp3.2中每次載入入口文件時都會進行錯誤和異常的捕獲,解讀這一部分代碼能夠對之後的優化頗有好處。
咱們嘗試在 Home/Index/index 下調用一個未定義的函數,會看到這樣的提示頁面:
咱們能夠看到tp3.2處理了致命異常的輸出,而且生成了一個提示頁面,咱們能夠經過入口文件很容易地找到tp3.2的致命錯誤的捕獲方法 Think/Library/Think/Think.class.php:
-
public
static
function start()
-
-
-
spl_autoload_register(
'Think\Think::autoload');
-
-
register_shutdown_function(
'Think\Think::fatalError');
-
set_error_handler(
'Think\Think::appError');
-
set_exception_handler(
'Think\Think::appException');
-
-
.........................
tp使用了 register_shutdown_function()來註冊一個在php停止時執行的函數,經過這個回調函數來捕獲了致命異常:
fatalError:
-
-
public
static
function fatalError()
-
-
-
-
if ($e = error_get_last()){
-
-
-
-
-
-
-
-
-
-
-
-
-
在這個方法中,tp使用error_get_last得到當前錯誤,而且使用ob_end_clean 丟掉緩衝區內容,阻止頁面上錯誤信息的輸出。
爲何ob_end_clean能夠阻止頁面輸出錯誤信息呢?這還得從php的緩衝區提及,
當PHP自身的緩衝區接到指令,指示要輸出緩衝區的內容時,將會把緩衝區內的數據輸出到apache上, apache接受到PHP輸出的數據,而後再把該數據存在到apache自身的緩衝區內,等到輸出
當apache接受到指令,只是要輸出緩衝區的內容時, 將會把緩衝區的內容輸出,返回到瀏覽器。而停止回調是做爲請求的一部分被執行的,所以能夠在它們中進行輸出或者讀取輸出緩衝區,咱們此時用ob_end_clean丟掉緩衝區的內容,就阻止了頁面的輸出顯示。(關於緩衝區:傳送門)
獲取到當前錯誤後,tp講這個錯誤傳遞個halt($e)這個靜態方法,這個方法其實就是tp的錯誤輸出處理了,咱們能夠從「處理概覽」圖中能夠看到,tp根據php不一樣的運行模式進行錯誤信息的處理與顯示:
halt:
-
-
-
-
-
-
public
static
function halt($error)
-
-
-
if (APP_DEBUG || IS_CLI) {
-
-
-
$trace = debug_backtrace();
-
-
$e[
'file'] = $trace[
0][
'file'];
-
$e[
'line'] = $trace[
0][
'line'];
-
-
-
$e[
'trace'] = ob_get_clean();
-
-
-
-
-
exit((IS_WIN ? iconv(
'UTF-8',
'gbk', $e[
'message']) : $e[
'message']) . PHP_EOL .
'FILE: ' . $e[
'file'] .
'(' . $e[
'line'] .
')' . PHP_EOL . $e[
'trace']);
-
-
-
-
$error_page = C(
'ERROR_PAGE');
-
if (!
empty($error_page)) {
-
-
-
$message = is_array($error) ? $error[
'message'] : $error;
-
$e[
'message'] = C(
'SHOW_ERROR_MSG') ? $message : C(
'ERROR_MESSAGE');
-
-
-
-
$exceptionFile = C(
'TMPL_EXCEPTION_FILE',
null, THINK_PATH .
'Tpl/think_exception.tpl');
-
-
-
APP_DEBUG 能夠在入口文件中修改,IS_CLI是 經過 php預約義常量 「PHP_SAPI」判斷當前php的運行環境,在框架入口文件ThinkPHP.php中是這樣配置的:
define('IS_CLI', PHP_SAPI == 'cli' ? 1 : 0);
(運行環境監測:
傳送門)
halt這個靜態方法內,根據php不一樣的運行環境處理傳遞過來的錯誤,命令行環境就這就退出打印,其餘模式就將錯誤信息返回給模塊頁面顯示。
調試模式中咱們能夠修改think_exception.tpl來調整咱們的頁面提示,非調試模式你也能夠調整think_exception.tpl模板,tp也給了一個錯誤頁面的配置,這些配置在慣例配置文件裏,咱們能夠自定義錯誤信息,也能夠指定錯誤後顯示的頁面。配置以下:
convention.php:
-
-
'ERROR_MESSAGE' =>
'頁面錯誤!請稍後再試~',
-
-
'SHOW_ERROR_MSG' =>
false,
2.自定義錯誤處理:
register_shutdown_down是處理「down」的,set_error_handler是處理「error」的,php的崩潰類型多種多樣,就拿錯誤類型的「E_USER_ERROR」來說,文前調用的一個未定義函數testErr()就是觸發的「down」裏面的 E_USER_ERROR,而咱們經過 trigger_error(‘’,E_USER_ERROR)就是觸發的「error」裏面的E_USER_ERROR,全部說自定義一個錯誤處理是頗有必要的,何況還有「NOTICE」這種類型的錯誤不會停止php執行就不能用「down」處理了呢?
咱們首先經過trigger_error()手動生成一個錯誤來看看tp是如何處理的,咱們嘗試在 Home/Index/index 裏寫下這樣一句代碼:
trigger_error ( "用戶自定義錯誤信息提示" , E_USER_ERROR );
運行結果以下:
從運行結果來看,與以前的致命錯誤"down"相比,這個錯誤提示頁面多了TRACE來顯示代碼執行流程,而且錯誤位置也放在了錯誤信息裏面(這個不重要,這個能夠隨便你拼接的),那麼咱們來看看 tp的自定義 錯誤處理:
appErr:
-
-
-
-
-
-
-
-
-
-
public
static
function appError($errno, $errstr, $errfile, $errline)
-
-
-
-
-
-
-
-
-
$errorStr =
"$errstr " . $errfile .
" 第 $errline 行.";
-
-
Log::write(
"[$errno] " . $errorStr, Log::ERR);
-
-
-
-
-
-
$errorStr =
"[$errno] $errstr " . $errfile .
" 第 $errline 行.";
-
self::trace($errorStr,
'',
'NOTIC');
-
-
-
首先咱們要知道的是,當前的靜態方法是set_error_handler()的回調方法,這個回調方法就包含了錯誤的error_handler(參數說明見代碼)。這個方法和以前的fatalError相比,不一樣的地方主要有兩個(記錄日誌會在之後的博客中說明):傳給halt()的參數變成了一個字符串(以前down處理了是包含錯誤信息的數組);NOTICE不在是經過halt去顯示了,而是調用了另一個方法,trace();
咱們先來看看第一個,傳一個字符串給用於顯示錯誤的halt()方法,咱們從上面的halt代碼塊中能夠看到這樣一段:
-
-
$trace = debug_backtrace();
-
-
$e[
'file'] = $trace[
0][
'file'];
-
$e[
'line'] = $trace[
0][
'line'];
-
-
-
$e[
'trace'] = ob_get_clean();
-
-
-
若是傳遞過來的參數不是數組,經過處理後$e就多一個成員['trace'],而咱們在錯誤模板中能夠發現,這個成員就是用於顯示咱們的代碼執行流程(追溯)的:
think_exception.tpl:
-
<?php
if(
isset($e[
'trace'])) {
?>
-
-
-
-
-
-
<p><?php echo nl2br($e['trace']);?></p>
-
-
-
那tp是如何去追溯這個代碼執行的呢?實際上是經過debug_backtrace()這個函數,debug_backtrace()產生一條回溯追蹤,說簡單點,就是個人這個錯誤是如何運行到這裏來的(因爲是回溯,通常返回的第一條就是產生錯誤的地方)。而後經過debug_print_backtrace()打印信息,在經過ob_get_clean獲得緩衝區內容並關閉緩衝區阻止瀏覽器的輸出,最後就在模板裏判斷是否存在$e['trace']來作輸出顯示。(不得不說,debug_backtrace是個調試神器)
若是是NOTICE級別的錯誤,就傳到了trace()方法,作日誌記錄。
trace:
-
-
-
-
-
-
-
-
-
public
static
function trace($value = '[think]', $label = '', $level = 'DEBUG', $record = false)
-
-
static $_trace =
array();
-
if (
'[think]' === $value) {
-
-
-
-
$info = ($label ? $label .
':' :
'') . print_r($value,
true);
-
$level = strtoupper($level);
-
-
if ((defined(
'IS_AJAX') && IS_AJAX) || !C(
'SHOW_PAGE_TRACE') || $record) {
-
Log::record($info, $level, $record);
-
-
if (!
isset($_trace[$level]) || count($_trace[$level]) > C(
'TRACE_MAX_RECORD')) {
-
$_trace[$level] =
array();
-
-
$_trace[$level][] = $info;
-
-
-
tp自定義了異常的處理,使用set_exception_handler()函數,設置了一個appException方法處理異常,咱們嘗試拋出一個異常,看tp的運行結果:
throw new \Exception('拋出一個異常')
能夠看到運行結果和「error」級別的處理很相似,咱們能夠看看使用set_exception_handler()設置的appException()方法:
-
public
static
function appException($e)
-
-
-
$error[
'message'] = $e->getMessage();
-
-
if (
'E' == $trace[
0][
'function']) {
-
$error[
'file'] = $trace[
0][
'file'];
-
$error[
'line'] = $trace[
0][
'line'];
-
-
$error[
'file'] = $e->getFile();
-
$error[
'line'] = $e->getLine();
-
-
$error[
'trace'] = $e->getTraceAsString();
-
Log::record($error[
'message'], Log::ERR);
-
-
header(
'HTTP/1.1 404 Not Found');
-
header(
'Status:404 Not Found');
-
-
首先咱們要知道,$e就是當前的異常對象,$e能夠調用該異常對象是方法,其中$e->getTrace()是追蹤包含異常信息的數組,追蹤信息中包含觸發異常的函數,tp判斷觸發該異常的函數是否是tp自帶的 E()函數,從而組裝異常信息發送給halt顯示,咱們能夠看到傳遞給halt是經過$e->getTraceAsString()獲取的字符串,因此halt後面又會用debug_backtrace()追溯異常,最後在頁面上生成TRACE信息。