結(jié)合項(xiàng)目中遇到的PHP異常處理問題改备,重新梳理了之前模糊的概念值朋,希望對(duì)大家理解PHP異常處理有所幫助。
請(qǐng)一定要注意艺沼,沒有特殊說明:本例?PHP Version < 7
說起PHP異常處理,大家首先會(huì)想到try-catch蕴掏,那好障般,我們先看一段程序吧:有一個(gè)test.php文件,有一段簡(jiǎn)單的PHP程序盛杰,內(nèi)容如下挽荡,然后命令行執(zhí)行:php test.php
1
5
6? ? } catch(Exception$e){7echo$e->getMessage();8? ? }9?>
我的問題是:這段程序能正確的捕捉到除0的錯(cuò)誤信息嗎?
如果你回答能即供,那你就把這篇文章看完吧定拟!應(yīng)該能學(xué)點(diǎn)東西。
本文章分5個(gè)部分介紹我的異常處理的理解:
PHP中什么是異常:
程序在運(yùn)行中出現(xiàn)不符合預(yù)期的情況需曾,允許發(fā)生(你也不想讓他出現(xiàn)不正常的情況)但他是一種不正常的情況,按照我們的正常邏輯本不該出的錯(cuò)誤祈远,但仍然會(huì)出現(xiàn)的錯(cuò)誤呆万,屬于邏輯和業(yè)務(wù)流程的錯(cuò)誤,而不是編譯或者語法上的錯(cuò)誤车份。
PHP中什么是錯(cuò)誤:
屬于php腳本自身的問題谋减,大部分情況是由錯(cuò)誤的語法,服務(wù)器環(huán)境導(dǎo)致扫沼,使得編譯器無法通過檢查出爹,甚至無法運(yùn)行的情況。warning缎除、notice都是錯(cuò)誤严就,只是他們的級(jí)別不同而已,并且錯(cuò)誤是不能被try-catch捕獲的器罐。
上面的說法是有前提條件的:
在PHP中梢为,因?yàn)樵谄渌Z言中就不能這樣下結(jié)論了,也就是說異常和錯(cuò)誤的說法在不同的語言有不同的說法。在PHP中任何自身的錯(cuò)誤或者是非正常的代碼都會(huì)當(dāng)做錯(cuò)誤對(duì)待铸董,并不會(huì)以異常的形式拋出祟印,但是也有一些情況會(huì)當(dāng)做異常和錯(cuò)誤同時(shí)拋出(據(jù)說是,我沒有找到合適的例子)粟害。也就是說蕴忆,你想在數(shù)據(jù)庫連接失敗的時(shí)候自動(dòng)捕獲異常是行不通的,因?yàn)檫@就不是異常悲幅,是錯(cuò)誤套鹅。但是在java中就不一樣了,他會(huì)把很多和預(yù)期不一致的行為當(dāng)做異常來進(jìn)行捕獲夺艰。
PHP異常處理很雞肋?
在上面的分析中我們可以看出沉衣,PHP并不能主動(dòng)的拋出異常郁副,但是你可以手動(dòng)拋出異常,這就很無語了豌习,如果你知道哪里會(huì)出問題存谎,你添加if else解決不就行了嗎,為啥還要手動(dòng)拋出異常肥隆,既然能手動(dòng)拋出就證明這個(gè)不是異常既荚,而是意料之中。以我的理解栋艳,這就是PHP異常處理雞肋的地方(不一定對(duì)扒∑浮)。所以PHP的異常機(jī)制不是那么的完美吸占,但是使用過框架的同學(xué)都知道有這個(gè)情況:你在框架中直接寫開頭那段php“自動(dòng)”捕獲異常的代碼是可以的晴叨,這是為什么?看過源碼的同學(xué)都知道框架中都會(huì)涉及三個(gè)函數(shù):register_shutdown_function矾屯,set_error_handler兼蕊,set_exception_handler后面我會(huì)重點(diǎn)講解著三個(gè)黑科技,通過這幾個(gè)函數(shù)我們可以實(shí)現(xiàn)PHP假自動(dòng)捕獲異常和錯(cuò)誤件蚕。
只有熟悉錯(cuò)誤級(jí)別才能對(duì)錯(cuò)誤捕捉有更好的認(rèn)識(shí)。 ERROR有不同的錯(cuò)誤級(jí)別排作,我之前的一篇文章中有寫到:http://www.cnblogs.com/zyf-zhaoyafei/p/3649434.html
下面我再總結(jié)性的給出這幾類錯(cuò)誤級(jí)別:
1Fatal Error:致命錯(cuò)誤(腳本終止運(yùn)行) 2E_ERROR// 致命的運(yùn)行錯(cuò)誤牵啦,錯(cuò)誤無法恢復(fù),暫停執(zhí)行腳本 3E_CORE_ERROR// PHP啟動(dòng)時(shí)初始化過程中的致命錯(cuò)誤 4E_COMPILE_ERROR// 編譯時(shí)致命性錯(cuò)妄痪,就像由Zend腳本引擎生成了一個(gè)E_ERROR 5E_USER_ERROR// 自定義錯(cuò)誤消息蕾久。像用PHP函數(shù)trigger_error(錯(cuò)誤類型設(shè)置為:E_USER_ERROR) 6 7? ? Parse Error:編譯時(shí)解析錯(cuò)誤,語法錯(cuò)誤(腳本終止運(yùn)行) 8E_PARSE//編譯時(shí)的語法解析錯(cuò)誤 910? ? Warning Error:警告錯(cuò)誤(僅給出提示信息,腳本不終止運(yùn)行)11E_WARNING// 運(yùn)行時(shí)警告 (非致命錯(cuò)誤)僧著。12E_CORE_WARNING// PHP初始化啟動(dòng)過程中發(fā)生的警告 (非致命錯(cuò)誤) 履因。13E_COMPILE_WARNING// 編譯警告14E_USER_WARNING// 用戶產(chǎn)生的警告信息1516? ? Notice Error:通知錯(cuò)誤(僅給出通知信息,腳本不終止運(yùn)行)17E_NOTICE// 運(yùn)行時(shí)通知盹愚。表示腳本遇到可能會(huì)表現(xiàn)為錯(cuò)誤的情況.18E_USER_NOTICE// 用戶產(chǎn)生的通知信息栅迄。
由此可知有5類是產(chǎn)生ERROR級(jí)別的錯(cuò)誤,這種錯(cuò)誤直接導(dǎo)致PHP程序退出皆怕。
可以定義成:
1ERROR =E_ERROR|E_CORE_ERROR|E_COMPILE_ERROR|E_USER_ERROR|E_PARSE
前面提到框架中是可以捕獲所有的錯(cuò)誤和異常的,之所以能實(shí)現(xiàn)應(yīng)該是使用了黑科技愈腾,哈哈憋活!其實(shí)也不是什么黑科技,主要是三個(gè)重要的函數(shù):
1:set_error_handler()
看到這個(gè)名字估計(jì)就知道什么意思了虱黄,這個(gè)函數(shù)用于捕獲錯(cuò)誤悦即,設(shè)置一個(gè)用戶自定義的錯(cuò)誤處理函數(shù)。
1set_error_handler: ' .$type. ':' .$message. ' in ' .$file. ' on ' .$line. ' line .
');6? ? }7?>
當(dāng)程序出現(xiàn)錯(cuò)誤的時(shí)候自動(dòng)調(diào)用此方法橱乱,不過需要注意一下兩點(diǎn):第一辜梳,如果存在該方法,相應(yīng)的error_reporting()就不能在使用了泳叠。所有的錯(cuò)誤都會(huì)交給自定義的函數(shù)處理作瞄。第二,此方法不能處理以下級(jí)別的錯(cuò)誤:E_ERROR危纫、 E_PARSE宗挥、 E_CORE_ERROR、 E_CORE_WARNING种蝶、 E_COMPILE_ERROR属韧、 E_COMPILE_WARNING,set_error_handler() 函數(shù)所在文件中產(chǎn)生的E_STRICT蛤吓,該函數(shù)只能捕獲系統(tǒng)產(chǎn)生的一些Warning宵喂、Notice級(jí)別的錯(cuò)誤。
并且他有多種調(diào)用的方法:
1
2:register_shutdown_function()
捕獲PHP的錯(cuò)誤:Fatal Error会傲、Parse Error等锅棕,這個(gè)方法是PHP腳本執(zhí)行結(jié)束前最后一個(gè)調(diào)用的函數(shù),比如腳本錯(cuò)誤淌山、die()裸燎、exit、異常泼疑、正常結(jié)束都會(huì)調(diào)用德绿,多么牛逼的一個(gè)函數(shù)啊!通過這個(gè)函數(shù)就可以在腳本結(jié)束前判斷這次執(zhí)行是否有錯(cuò)誤產(chǎn)生移稳,這時(shí)就要借助于一個(gè)函數(shù):error_get_last()蕴纳;這個(gè)函數(shù)可以拿到本次執(zhí)行產(chǎn)生的所有錯(cuò)誤。error_get_last();返回的信息:
[type]?????????? - 錯(cuò)誤類型
[message] - 錯(cuò)誤消息
[file]????????????? - 發(fā)生錯(cuò)誤所在的文件
[line] ? ? ? ? ? ? - 發(fā)生錯(cuò)誤所在的行
1register_shutdown_function: Type:' .$error['type'] . ' Msg: ' .$error['message'] . ' in ' .$error['file'] . ' on line ' .$error['line'] . '');7? ? ? ? }8? ? }9?>
通過這種方法就可以巧妙的打印出程序結(jié)束前所有的錯(cuò)誤信息个粱。但是我在測(cè)試的時(shí)候我發(fā)現(xiàn)并不是所有的錯(cuò)誤終止后都會(huì)調(diào)用這個(gè)函數(shù)古毛,可以看下面的一個(gè)測(cè)試文件,內(nèi)容是:
1register_shutdown_function: Type:' .$error['type'] . ' Msg: ' .$error['message'] . ' in ' .$error['file'] . ' on line ' .$error['line'] . ''); 7? ? ? ? } 8? ? } 9var_dump(23+-+);//此處語法錯(cuò)誤10?>
自己可以試一下都许,你可以看到根本就不會(huì)觸發(fā)zyfshutdownfunc()函數(shù)稻薇,其實(shí)這是一個(gè)語法錯(cuò)誤,直接報(bào)了一個(gè):
1
由此引出一個(gè)奇葩的問題:?jiǎn)柺裁床荒苡|發(fā)胶征,為什么框架中是可以的塞椎?其實(shí)原因很簡(jiǎn)單,只在parse-time出錯(cuò)時(shí)是不會(huì)調(diào)用本函數(shù)的睛低。只有在run-time出錯(cuò)的時(shí)候案狠,才會(huì)調(diào)用本函數(shù),我的理解是語法檢查器前沒有執(zhí)行register_shutdown_function()去把需要注冊(cè)的函數(shù)放到調(diào)用的堆棧中暇昂,所以就根本不會(huì)運(yùn)行莺戒。那框架中為什么任何錯(cuò)誤都能進(jìn)入到register_shutdown_function()中呢伴嗡,其實(shí)在框架中一般會(huì)有統(tǒng)一的入口index.php,然后每個(gè)類庫文件都會(huì)通過include ** 的方式加載到index.php中,相當(dāng)與所有的程序都會(huì)在index.php中聚集衙熔,同樣九孩,你寫的具有語法錯(cuò)誤的文件也會(huì)被引入到入口文件中,這樣的話阱扬,調(diào)用框架泣懊,執(zhí)行index.php,index.php本身并沒有語法錯(cuò)誤麻惶,也就不會(huì)產(chǎn)生parse-time錯(cuò)誤馍刮,而是 include 文件出錯(cuò)了,是run-time的時(shí)候出錯(cuò)了窃蹋,所以框架執(zhí)行完之后就會(huì)觸發(fā)register_shutdown_function();
所以現(xiàn)在可是試一下這個(gè)寫法卡啰,這樣就會(huì)觸發(fā)zyfshutdownfunc()回調(diào)了:
1a.php文件 2 6 7b.php文件 8register_shutdown_function: Type:' .$error['type'] . ' Msg: ' .$error['message'] . ' in ' .$error['file'] . ' on line ' .$error['line'] . '');14? ? ? ? }15? ? }16require'a.php';17?>
3:set_exception_handler()
設(shè)置默認(rèn)的異常處理程序,用在沒有用try/catch塊來捕獲的異常警没,也就是說不管你拋出的異常有沒有人捕獲匈辱,如果沒有人捕獲就會(huì)進(jìn)入到該方法中,并且在回調(diào)函數(shù)調(diào)用后異常會(huì)中止杀迹⊥隽常看一下用法:
1set_exception_handler: Exception: " .$exception->getMessage()? . '');6? ? }7thrownewException("zyf exception");8?>
1:把錯(cuò)誤以異常的形式拋出(不能完全拋出)
由上面的講解我們知道,php中的錯(cuò)誤是不能以異常的像是捕獲的浅碾,但是我們需要讓他們拋出大州,已達(dá)到擴(kuò)展 try-catch的影響范圍,我們前面講到過set_error_handler() 方法及穗,他是干嘛用的摧茴,他是捕獲錯(cuò)誤的,所以我們就可以借助他來吧錯(cuò)誤捕獲埂陆,然后再以異常的形式拋出苛白,ok,試試下面的寫法:
1getMessage();14? ? }15?>
好了焚虱,試一下购裙,會(huì)打印出:
1Division by zero zyf123
流程:本來是除0錯(cuò)誤,然后觸發(fā)set_error_handler()鹃栽,在set_error_handler()中相當(dāng)與殺了個(gè)回馬槍躏率,再把錯(cuò)誤信息以異常的形式拋出來,這樣就可以實(shí)現(xiàn)錯(cuò)誤以異常的形式拋出民鼓。大家要注意:這樣做是有缺點(diǎn)的薇芝,會(huì)受到set_error_handler()函數(shù)捕獲級(jí)別的限制。
?2:捕獲所有的錯(cuò)誤
由set_error_handler()可知丰嘉,他能夠捕獲一部分錯(cuò)誤夯到,不能捕獲系統(tǒng)級(jí)E_ERROR、E_PARSE等錯(cuò)誤饮亏,但是這部分可以由register_shutdown_function()捕獲耍贾。所以兩者結(jié)合能出現(xiàn)很好的功能。
看下面的程序:
1a.php內(nèi)容: 2
21b.php內(nèi)容:22register_shutdown_function: Type:' .$error['type'] . ' Msg: ' .$error['message'] . ' in ' .$error['file'] . ' on line ' .$error['line'] . '');29? ? ? ? }30? ? }3132set_error_handler('zyferror');33functionzyferror($type,$message,$file,$line)34? ? {35var_dump('set_error_handler: ' .$type. ':' .$message. ' in ' .$file. ' on ' .$line. ' line .
');36? ? }3738require'a.php';39?>
到此就可以解釋開頭的那個(gè)程序了吧路幸,test.php 如果是單文件執(zhí)行是不能捕獲到錯(cuò)誤的荐开,如果你在框架中執(zhí)行就是可以的,當(dāng)然你按照我上面介紹的來擴(kuò)展也是可以的简肴。
1:自定義異常處理
在復(fù)雜的系統(tǒng)中,我們往往需要自己捕獲我們需要特殊處理的異常砰识,這些異衬馨牵可能是特殊情況下拋出的。所以我們就自己定義一個(gè)異常捕獲類仍翰,該類必須是 exception 類的一個(gè)擴(kuò)展赫粥,該類繼承了 PHP 的 exception 類的所有屬性,并且我們可以添加自定義的函數(shù)予借,使用的時(shí)候其實(shí)和之前的一樣越平,大致寫法如下:
1getLine().' in ' .$this->getFile() 7.': ' .$this->getMessage() . ' Must in (0 - 60)'; 8? ? ? ? } 9? ? }1011$age= 10;12try {13$age=intval($age);14if($age> 60) {15thrownewzyfException($age);16? ? ? ? }1718}catch(zyfException$e) {19echo$e->errorzyfMessage();2021? ? }22?>
2:異常嵌套
異常嵌套是比較常見的寫法频蛔,在自定義的異常處理中,try 塊中可以定義多個(gè)異常捕獲秦叛,然后分層傳遞異常晦溪,理解和冒泡差不多,看下面的實(shí)現(xiàn):
1 60) { 6thrownewzyfException($age); 7? ? ? ? } 8 9if($age<= 0) {10thrownewException($age . ' must > 0');11? ? ? ? }1213}catch(zyfException$e) {14echo$e->errorzyfMessage();1516}catch(Exception$e) {17echo$e->getMessage();18? ? }19?>
當(dāng)然也可以在catch中再拋出異常給上層:
1 60) { 7thrownewException($age); 8? ? ? ? ? ? } 910}catch(Exception$e) {11thrownewzyfException($age);1213? ? ? ? }1415}catch(zyfException$e) {16echo$e->errorzyfMessage();17? ? }18?>
現(xiàn)在寫PHP必須考慮版本情況三圆,上面的寫法在PHP7中大部分都能實(shí)現(xiàn),但是也會(huì)有不同點(diǎn)避咆,在PHP7更新中有一條:更多的Error變?yōu)榭刹东@的Exception舟肉,現(xiàn)在的PHP7實(shí)現(xiàn)了一個(gè)全局的throwable接口,原來老的Exception和其中一部分Error實(shí)現(xiàn)了這個(gè)接口(interface)查库,PHP7中更多的Error變?yōu)榭刹东@的Exception返回給捕捉器路媚,這樣其實(shí)和前面提到的擴(kuò)展try-catch影響范圍一樣,但是如果不捕獲則還是按照Error對(duì)待樊销,看下面兩個(gè):
1getMessage() . ' zyf'; 7? ? } 8 9try {10? ? ? ? test();1112}catch(Error$e) {13echo$e->getMessage() . ' zyf';14? ? }15?>
因?yàn)镻HP7實(shí)現(xiàn)了throwable接口整慎,那么就可以使用第一個(gè)這種方式來捕獲異常。又因?yàn)椴糠諩rror實(shí)現(xiàn)了接口围苫,并且更多的Error變?yōu)榭刹东@的Exception裤园,那么就可以使用第二種方式來捕獲異常。下面是在網(wǎng)上找的PHP7的異常層次樹:
Throwable
Exception 異常
...
Error 錯(cuò)誤
ArithmeticError 算數(shù)錯(cuò)誤
DivisionByZeroError 除數(shù)為0的錯(cuò)誤
AssertionError 聲明錯(cuò)誤
ParseError 解析錯(cuò)誤
TypeError 類型錯(cuò)誤
?就寫到這吧剂府,寫得手疼拧揽,關(guān)于錯(cuò)誤和異常處理的大致就寫這么多,有什么錯(cuò)誤請(qǐng)?jiān)谠u(píng)論中給出周循,多謝大家强法。