1 錯(cuò)誤
1.1 簡介
在 PHP
中,默認(rèn)的錯(cuò)誤處理很簡單。一條錯(cuò)誤消息會(huì)被發(fā)送到瀏覽器卫玖,這條消息帶有文件名、行號以及描述錯(cuò)誤的消息蠢正。
在創(chuàng)建腳本和 Web 應(yīng)用程序時(shí)骇笔,錯(cuò)誤處理是一個(gè)重要的部分省店。如果代碼缺少錯(cuò)誤檢測編碼嚣崭,那么程序看上去很不專業(yè),也為安全風(fēng)險(xiǎn)敞開了大門懦傍。
1.2 簡單錯(cuò)誤處理
1.2.1 使用die
如下實(shí)例展示了一個(gè)打開文本文件的簡單腳本:
<?php
$file=fopen("welcome.txt","r");
?>
如果文件不存在辜王,會(huì)得到類似這樣的錯(cuò)誤:
Warning: fopen(welcome.txt) [function.fopen]: failed to open stream:
No such file or directory in /www/runoob/test/test.php on line 2
為了避免用戶得到類似上面的錯(cuò)誤消息绝页,我們在訪問文件之前檢測該文件是否存在:
<?php
if(!file_exists("welcome.txt"))
{
die("文件不存在");
}
else
{
$file=fopen("welcome.txt","r");
}
?>
現(xiàn)在,如果文件不存在,會(huì)得到類似這樣的錯(cuò)誤消息:文件不存在
相比之前的代碼儡嘶,上面的代碼更有效,這是由于它采用了一個(gè)簡單的錯(cuò)誤處理機(jī)制在錯(cuò)誤之后終止了腳本纵顾。
1.2.2 die和exit區(qū)別
在 PHP
中敬鬓,die
和 exit
是幾乎相同的功能,它們都用于終止腳本的執(zhí)行
偏塞。兩者在功能上沒有本質(zhì)的區(qū)別唱蒸,它們都是語言結(jié)構(gòu)(不是函數(shù))
,都可以接受一個(gè)可選的字符串參數(shù)作為退出信息灸叼。
-
die()
die()
是exit()
的別名神汹。當(dāng)調(diào)用die()
時(shí)庆捺,PHP
會(huì)輸出傳遞給它的字符串(如果有的話),然后終止腳本的執(zhí)行屁魏。 -
exit()
exit()
也是用來終止腳本的執(zhí)行的滔以。可以給它傳遞一個(gè)狀態(tài)碼(一個(gè)整數(shù))或一個(gè)字符串作為退出信息氓拼。如果傳遞了一個(gè)字符串你画,該字符串會(huì)被輸出。
php
<?php
exit("腳本已終止"); // 輸出 "腳本已終止" 然后終止
exit(1); // 終止腳本并返回狀態(tài)碼 1
?>
注意事項(xiàng):
如果 exit 或 die
被調(diào)用在一個(gè)包含有 return
語句的函數(shù)中披诗,腳本也會(huì)終止撬即,但 return
語句不會(huì)被執(zhí)行。但是呈队,如果 exit 或 die
語句在 return
語句之后剥槐,那么 exit 或 die
實(shí)際上永遠(yuǎn)不會(huì)被執(zhí)行,因?yàn)楹瘮?shù)在 return
語句執(zhí)行時(shí)就已經(jīng)結(jié)束了
使用 exit 或 die
時(shí)要小心宪摧,因?yàn)樗鼈儠?huì)立即停止腳本的執(zhí)行粒竖,可能會(huì)導(dǎo)致數(shù)據(jù)丟失或其他未預(yù)期的副作用。在可能的情況下几于,最好使用更精細(xì)的控制流結(jié)構(gòu)(如 if 語句蕊苗、break、continue 等)來管理腳本的流程沿彭。
1.3 自定義錯(cuò)誤處理
1.3.1 定義
創(chuàng)建一個(gè)自定義的錯(cuò)誤處理器非常簡單朽砰。創(chuàng)建了一個(gè)專用函數(shù),可以在 PHP 中發(fā)生錯(cuò)誤時(shí)調(diào)用該函數(shù)喉刘。
該函數(shù)必須有能力處理至少兩個(gè)參數(shù) (error_level 和 error_message
)瞧柔,但是可以接受最多五個(gè)參數(shù)(可選的:file, line-number 和 error context
):
語法:
error_function(error_level,error_message,error_file,error_line,error_context)
參數(shù) | 描述 |
---|---|
error_level | 必需。為用戶定義的錯(cuò)誤規(guī)定錯(cuò)誤報(bào)告級別睦裳。必須是一個(gè)數(shù)字 |
error_message | 必需造锅。為用戶定義的錯(cuò)誤規(guī)定錯(cuò)誤消息 |
error_file | 可選。規(guī)定錯(cuò)誤發(fā)生的文件名 |
error_line | 可選廉邑。規(guī)定錯(cuò)誤發(fā)生的行號 |
error_context | 可選哥蔚。規(guī)定一個(gè)數(shù)組,包含了當(dāng)錯(cuò)誤發(fā)生時(shí)在用的每個(gè)變量以及它們的值 |
錯(cuò)誤報(bào)告級別
這些錯(cuò)誤報(bào)告級別是用戶自定義的錯(cuò)誤處理程序處理的不同類型的錯(cuò)誤:
值 | 常量 | 描述 |
---|---|---|
2 | E_WARNING | 非致命的 run-time 錯(cuò)誤蛛蒙。不暫停腳本執(zhí)行 |
8 | E_NOTICE | run-time 通知糙箍。在腳本發(fā)現(xiàn)可能有錯(cuò)誤時(shí)發(fā)生,但也可能在腳本正常運(yùn)行時(shí)發(fā)生 |
256 | E_USER_ERROR | 致命的用戶生成的錯(cuò)誤牵祟。這類似于程序員使用 PHP 函數(shù) trigger_error() 設(shè)置的 E_ERROR |
512 | E_USER_WARNING | 非致命的用戶生成的警告深夯。這類似于程序員使用 PHP 函數(shù) trigger_error() 設(shè)置的 E_WARNING |
1024 | E_USER_NOTICE | 用戶生成的通知。這類似于程序員使用 PHP 函數(shù) trigger_error() 設(shè)置的 E_NOTICE |
4096 | E_RECOVERABLE_ERROR | 可捕獲的致命錯(cuò)誤课舍。類似 E_ERROR塌西,但可被用戶定義的處理程序捕獲 |
8191 | E_ALL | 所有錯(cuò)誤和警告他挎。(在 PHP 5.4 中,E_STRICT 成為 E_ALL 的一部分) |
1.3.2 創(chuàng)建錯(cuò)誤函數(shù)
創(chuàng)建一個(gè)處理錯(cuò)誤的函數(shù):
function customError($errno, $errstr)
{
echo "<b>Error:</b> [$errno] $errstr<br>";
echo "腳本結(jié)束";
die();
}
上面的代碼是一個(gè)簡單的錯(cuò)誤處理函數(shù)捡需。當(dāng)它被觸發(fā)時(shí)办桨,它會(huì)取得錯(cuò)誤級別和錯(cuò)誤消息。然后它會(huì)輸出錯(cuò)誤級別和消息站辉,并終止腳本呢撞。
現(xiàn)在,需要確定在何時(shí)觸發(fā)該函數(shù)饰剥。
PHP
的默認(rèn)錯(cuò)誤處理程序是內(nèi)建的錯(cuò)誤處理程序殊霞。打算把上面的函數(shù)改造為腳本運(yùn)行期間的默認(rèn)錯(cuò)誤處理程序√兀可以修改錯(cuò)誤處理程序绷蹲,使其僅應(yīng)用到某些錯(cuò)誤,這樣腳本就能以不同的方式來處理不同的錯(cuò)誤顾孽。然而祝钢,在本例中,打算針對所有錯(cuò)誤來使用我們自定義的錯(cuò)誤處理程序:
set_error_handler("customError");
由于我們希望我們的自定義函數(shù)能處理所有錯(cuò)誤若厚,set_error_handler()
僅需要一個(gè)參數(shù)拦英,可以添加第二個(gè)參數(shù)來規(guī)定錯(cuò)誤級別
。
通過嘗試輸出不存在的變量测秸,來測試這個(gè)錯(cuò)誤處理程序:
<?php
// 錯(cuò)誤處理函數(shù)
function customError($errno, $errstr)
{
echo "<b>Error:</b> [$errno] $errstr";
}
// 設(shè)置錯(cuò)誤處理函數(shù)
set_error_handler("customError");
// 觸發(fā)錯(cuò)誤
echo($test);
?>
1.4 觸發(fā)錯(cuò)誤
在腳本中用戶輸入數(shù)據(jù)的位置疤估,當(dāng)用戶的輸入無效時(shí)觸發(fā)錯(cuò)誤是很有用的。在 PHP
中霎冯,這個(gè)任務(wù)由 trigger_error()
函數(shù)完成铃拇。
在本例中,如果 "test" 變量大于 "1"肃晚,就會(huì)發(fā)生錯(cuò)誤:
<?php
$test=2;
if ($test>1)
{
trigger_error("變量值必須小于等于 1");
}
?>
以上代碼的輸出如下所示:
Notice: 變量值必須小于等于 1
in /www/test/test.php on line 5
可以在腳本中任何位置觸發(fā)錯(cuò)誤锚贱,通過添加的第二個(gè)參數(shù)仔戈,能夠規(guī)定所觸發(fā)的錯(cuò)誤類型关串。
1.5 抑制錯(cuò)誤
1.5.1 行內(nèi)錯(cuò)誤抑制
可以讓 PHP
利用錯(cuò)誤控制操作符 @
來抑制特定的錯(cuò)誤。將這個(gè)操作符放置在表達(dá)式之前监徘,其后的任何錯(cuò)誤都不會(huì)出現(xiàn)晋修。
<?php
echo @$foo['bar'];
如果 $foo['bar']
存在,程序會(huì)將結(jié)果輸出凰盔,如果變量 $foo
或是 'bar'
鍵值不存在墓卦,則會(huì)返回 null 并且不輸出任何東西。如果不使用錯(cuò)誤控制操作符户敬,這個(gè)表達(dá)式會(huì)產(chǎn)生一個(gè)錯(cuò)誤信息 PHP Notice: Undefined variable: foo 或 PHP Notice: Undefined index: bar
落剪。
注意
:PHP
處理使用 @
的表達(dá)式比起不用時(shí)效率會(huì)低一些睁本。
錯(cuò)誤控制操作符會(huì) 完全
吃掉錯(cuò)誤。不但沒有顯示忠怖,而且也不會(huì)記錄在錯(cuò)誤日志中呢堰。此外,在正式環(huán)境中 PHP
也沒有辦法關(guān)閉錯(cuò)誤控制操作符凡泣。也許那些錯(cuò)誤是無害的枉疼,不過那些較具傷害性的錯(cuò)誤同時(shí)也會(huì)被隱藏。
2 異常
2.1 引言
異常是許多流行編程語言的標(biāo)配鞋拟,但它們往往被 PHP 開發(fā)人員所忽視骂维。像 Ruby 就是一個(gè)極度重視異常的語言,無論有什么錯(cuò)誤發(fā)生贺纲,像是 HTTP 請求失敗航闺,或者數(shù)據(jù)庫查詢有問題,甚至找不到一個(gè)圖片資源猴誊,Ruby (或是所使用的 gems)来颤,將會(huì)拋出異常,可以通過屏幕立刻知道所發(fā)生的問題稠肘。
PHP
處理這個(gè)問題則比較隨意福铅,調(diào)用 file_get_contents()
函數(shù)通常只會(huì)給出 FALSE
值和警告。許多較早的 PHP 框架比如 CodeIgniter 只是返回 false项阴,將信息寫入專有的日志滑黔,或者使用類似 $this->upload->get_error()
的方法來查看錯(cuò)誤原因。這里的問題在于必須找出錯(cuò)誤所在环揽,并且通過翻閱文檔來查看這個(gè)類使用了什么樣的錯(cuò)誤的方法略荡,而不是明確的暴露錯(cuò)誤。
2.2 什么是異常
PHP 5
提供了一種新的面向?qū)ο蟮腻e(cuò)誤處理方法歉胶。
異常處理
用于在指定的錯(cuò)誤(異常)情況發(fā)生時(shí)改變腳本的正常流程汛兜。這種情況稱為異常。
當(dāng)異常被觸發(fā)時(shí)通今,通常會(huì)發(fā)生:
- 當(dāng)前代碼狀態(tài)被保存
- 代碼執(zhí)行被切換到預(yù)定義(自定義)的異常處理器函數(shù)
- 根據(jù)情況粥谬,處理器也許會(huì)從保存的代碼狀態(tài)重新開始執(zhí)行代碼,終止腳本執(zhí)行辫塌,或從代碼中另外的位置繼續(xù)執(zhí)行腳本
當(dāng)異常被拋出時(shí)漏策,其后的代碼不會(huì)繼續(xù)執(zhí)行,PHP
會(huì)嘗試查找匹配的 catch
代碼塊臼氨。如果異常沒有被捕獲掺喻,而且又沒用使用 set_exception_handler()
作相應(yīng)的處理的話,那么將發(fā)生一個(gè)嚴(yán)重的錯(cuò)誤(致命錯(cuò)誤),并且輸出 Uncaught Exception
(未捕獲異常)的錯(cuò)誤消息感耙。
2.3 Try褂乍、throw、catch即硼、finally
要避免上面實(shí)例中出現(xiàn)的錯(cuò)誤树叽,我們需要?jiǎng)?chuàng)建適當(dāng)?shù)拇a來處理異常。
適當(dāng)?shù)奶幚懋惓4a應(yīng)該包括:
-
Try
:使用異常的函數(shù)應(yīng)該位于 "try" 代碼塊內(nèi)谦絮。如果沒有觸發(fā)異常题诵,則代碼將照常繼續(xù)執(zhí)行。但是如果異常被觸發(fā)层皱,會(huì)拋出一個(gè)異常性锭。 -
Throw
:這里規(guī)定如何觸發(fā)異常。每一個(gè) "throw" 必須對應(yīng)至少一個(gè) "catch"叫胖。 -
Catch
:捕獲異常草冈,并創(chuàng)建一個(gè)包含異常信息的對象。 -
finally
:用于包含無論是否發(fā)生異常都需要執(zhí)行的代碼瓮增。這通常用于清理資源怎棱,如關(guān)閉文件句柄、數(shù)據(jù)庫連接或執(zhí)行其他必須完成的操作
讓我們觸發(fā)一個(gè)異常:
<?php
// 創(chuàng)建一個(gè)有異常處理的函數(shù)
function checkNum($number)
{
if($number>1)
{
throw new Exception("變量值必須小于等于 1");
}
return true;
}
// 在 try 塊 觸發(fā)異常
try
{
checkNum(2);
// 如果拋出異常绷跑,以下文本不會(huì)輸出
echo '如果輸出該內(nèi)容拳恋,說明 $number 變量';
}
// 捕獲異常
catch(Exception $e)
{
echo 'Message: ' .$e->getMessage();
}
?>
上面代碼將得到類似這樣一個(gè)錯(cuò)誤:Message: 變量值必須小于等于 1
2.4 自定義異常
創(chuàng)建自定義的異常處理程序非常簡單。創(chuàng)建了一個(gè)專門的類砸捏,當(dāng) PHP
中發(fā)生異常時(shí)谬运,可調(diào)用其函數(shù)。該類必須是 exception
類的一個(gè)擴(kuò)展垦藏。
這個(gè)自定義的 customException
類繼承了 PHP
的 exception
類的所有屬性梆暖,可向其添加自定義的函數(shù)。
<?php
class customException extends Exception
{
public function errorMessage()
{
// 錯(cuò)誤信息
$errorMsg = '錯(cuò)誤行號 '.$this->getLine().' in '.$this->getFile()
.': <b>'.$this->getMessage().'</b> 不是一個(gè)合法的 E-Mail 地址';
return $errorMsg;
}
}
$email = "someone@example...com";
try
{
// 檢測郵箱
if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
{
// 如果是個(gè)不合法的郵箱地址掂骏,拋出異常
throw new customException($email);
}
}
catch (customException $e)
{
//display custom message
echo $e->errorMessage();
}
?>
上面的代碼拋出了一個(gè)異常轰驳,并通過一個(gè)自定義的 exception 類來捕獲它:
- customException() 類是作為舊的 exception 類的一個(gè)擴(kuò)展來創(chuàng)建的。這樣它就繼承了舊的 exception 類的所有屬性和方法弟灼。
- 創(chuàng)建 errorMessage() 函數(shù)级解。如果
e-mail
地址不合法,則該函數(shù)返回一條錯(cuò)誤消息袜爪。 - 把
$email
變量設(shè)置為不合法的 e-mail 地址字符串蠕趁。 - 執(zhí)行 "try" 代碼塊薛闪,由于
e-mail
地址不合法辛馆,因此拋出一個(gè)異常。 - "catch" 代碼塊捕獲異常,并顯示錯(cuò)誤消息昙篙。
2.5 設(shè)置頂層異常處理器
set_exception_handler()
函數(shù)可設(shè)置處理所有未捕獲異常的用戶定義函數(shù)腊状。
<?php
function myException($exception)
{
echo "<b>Exception:</b> " , $exception->getMessage();
}
set_exception_handler('myException');
throw new Exception('Uncaught Exception occurred');
?>
以上代碼的輸出如下所示:Exception: Uncaught Exception occurred
在上面的代碼中,不存在 "catch" 代碼塊苔可,而是觸發(fā)頂層的異常處理程序缴挖。應(yīng)該使用此函數(shù)來捕獲所有未被捕獲的異常。
3 錯(cuò)誤與異撤俑ǎ總結(jié)
3.1 區(qū)別
在 PHP
中映屋,錯(cuò)誤(Errors
)和異常(Exceptions
)是兩個(gè)不同的概念,用于處理不同類型的運(yùn)行時(shí)問題同蜻。它們之間的主要區(qū)別如下:
- 錯(cuò)誤(
Errors
)- 錯(cuò)誤通常是更低級別的棚点、更嚴(yán)重的運(yùn)行時(shí)問題,這些問題通常是由于
PHP
引擎內(nèi)部發(fā)生的問題或違反了語言規(guī)則導(dǎo)致的湾蔓。 - 錯(cuò)誤包括如類型錯(cuò)誤(
Type Errors
)瘫析、致命錯(cuò)誤(Fatal Errors
)、解析錯(cuò)誤(Parse Errors
)等默责。 - 錯(cuò)誤通常不能被捕獲(除了使用特殊的錯(cuò)誤處理機(jī)制贬循,如
register_shutdown_function() 和 error_get_last()
),并且它們會(huì)終止腳本的執(zhí)行桃序。 -
PHP 7
引入了可捕獲的錯(cuò)誤(Catchable Errors
)杖虾,這些錯(cuò)誤可以通過 Error 類進(jìn)行捕獲和處理,但它們在本質(zhì)上仍然是錯(cuò)誤媒熊,而不是異常亏掀。
- 錯(cuò)誤通常是更低級別的棚点、更嚴(yán)重的運(yùn)行時(shí)問題,這些問題通常是由于
- 異常(Exceptions)
- 異常是程序執(zhí)行過程中出現(xiàn)的異常情況,通常是由于程序邏輯錯(cuò)誤或不可預(yù)見的條件導(dǎo)致的泛释。
- 異陈算担可以被拋出(
throw
)和捕獲(catch
),允許開發(fā)者在異常發(fā)生時(shí)采取適當(dāng)?shù)拇胧┝#缬涗浫罩炯溆啊⒒貪L事務(wù)或執(zhí)行其他恢復(fù)操作。 - 異城炎拢可以通過繼承
Exception
類或其子類來定義自定義的異常類型魂贬。 - 當(dāng)異常被拋出時(shí),程序的執(zhí)行會(huì)立即停止當(dāng)前的操作裙顽,并跳轉(zhuǎn)到最近的匹配的
catch
塊(如果存在)付燥。如果沒有匹配的catch
塊,異常將冒泡到調(diào)用棧的上一層愈犹,直到被捕獲或到達(dá)腳本的頂層并導(dǎo)致腳本終止键科。
區(qū)別
- 嚴(yán)重性和級別:錯(cuò)誤通常表示更嚴(yán)重的闻丑、更低級別的問題,而異常通常表示程序邏輯上的錯(cuò)誤或異常情況勋颖。
- 處理方式:錯(cuò)誤通常不能被直接捕獲(除了可捕獲的錯(cuò)誤)嗦嗡,而異常可以通過
try/catch
塊進(jìn)行捕獲和處理饭玲。 - 中斷執(zhí)行:
錯(cuò)誤通常會(huì)立即終止腳本的執(zhí)行
侥祭,而異常可以通過捕獲來避免腳本的終止茄厘,并允許開發(fā)者執(zhí)行恢復(fù)操作矮冬。 - 自定義性:異常可以通過繼承
Exception
類或其子類來定義自定義的異常類型次哈,而錯(cuò)誤通常不能自定義(除了用戶定義的錯(cuò)誤處理器)欢伏。