PHP 有一個(gè)和其他語(yǔ)言相似的異常模型。在 PHP 里可以 throw 并捕獲(catch)異常。為了捕獲潛在的異常,代碼會(huì)包含在 try 塊里。每個(gè) try 都必須至少有一個(gè)相應(yīng)的 catch 或 finally 塊。
如果拋出異常的函數(shù)作用域內(nèi)沒(méi)有 catch 塊,異常會(huì)沿調(diào)用?!跋蛏厦芭荨?,直到找到匹配的 catch 塊。沿途會(huì)執(zhí)行所有遇到的 finally 塊。在沒(méi)有設(shè)置全局異常處理程序時(shí),如果調(diào)用棧向上都沒(méi)有遇到匹配的 catch,程序會(huì)拋出 fatal 錯(cuò)誤并終止。
拋出的對(duì)象必須是 instanceof Throwable。嘗試拋出其他對(duì)象會(huì)導(dǎo)致 PHP Fatal 錯(cuò)誤。
PHP 8.0.0 起,throw 關(guān)鍵詞現(xiàn)在開(kāi)始是表達(dá)式,可用于任何表達(dá)式上下文。在此之前,它是語(yǔ)句,必須獨(dú)占一行。
一、catch
catch 定義了處理拋出異常的方式。 catch 塊定義了它能處理的異常/錯(cuò)誤的類(lèi)型,并可以選擇將異常賦值到變量中。 (在 PHP 8.0.0 之前的版本中必須要賦值到變量) 如果遇到拋出對(duì)象的類(lèi)型匹配了首個(gè) catch 塊的異?;蝈e(cuò)誤,將會(huì)處理該對(duì)象。
可用多個(gè) catch 捕獲不同的異常類(lèi)。 正常情況下(try 代碼塊里沒(méi)有拋出異常)會(huì)在最后一個(gè)定義的 catch 后面繼續(xù)執(zhí)行。 catch 代碼塊里也可以 throw 或者重新拋出異常。 不拋出的話(huà),會(huì)在觸發(fā)的 catch 后面繼續(xù)執(zhí)行。
當(dāng) PHP 拋出一個(gè)異常時(shí),將不會(huì)執(zhí)行后續(xù)的代碼語(yǔ)句,并會(huì)嘗試查找首個(gè)匹配的 catch 代碼塊。 如果沒(méi)有用 set_exception_handler() 設(shè)置異常處理函數(shù), PHP 會(huì)在異常未被捕獲時(shí)產(chǎn)生 Fatal 級(jí)錯(cuò)誤,提示 “Uncaught Exception …” 消息。
從 PHP 7.1.0 起 catch 可以用豎線符(|) 指定多個(gè)異常。 如果在不同的類(lèi)層次結(jié)構(gòu)中,不同異常的異常需要用同樣的方式處理,就特別適用這種方式。
從 PHP 8.0.0 起,捕獲的異常不再?gòu)?qiáng)制要求指定變量名。 catch 代碼塊會(huì)在未指定時(shí)繼續(xù)執(zhí)行,只是無(wú)法訪問(wèn)到拋出的對(duì)象。
二、finally
finally 代碼塊可以放在 catch 之后,或者直接代替它。 無(wú)論是否拋出了異常,在 try 和 catch 之后、在執(zhí)行后續(xù)代碼之前, 放在 finally 里的代碼總是會(huì)執(zhí)行。
值得注意的是 finally 和 return 語(yǔ)句之間存在相互影響。 如果在 try 或 catch 里遇到 return,仍然會(huì)執(zhí)行 finally 里的代碼。 而且,遇到 return 語(yǔ)句時(shí),會(huì)先執(zhí)行 finally 再返回結(jié)果。 此外,如果 finally 里也包含了 return 語(yǔ)句,將返回 finally 里的值。
三、全局異常處理程序
當(dāng)允許異常冒泡到全局作用域時(shí),它可以被全局異常處理器捕獲到。 set_exception_handler() 可以設(shè)置一個(gè)函數(shù),在沒(méi)有調(diào)用其他塊時(shí)代替 catch。 在本質(zhì)上,實(shí)現(xiàn)的效果等同于整個(gè)程序被 try-catch 包裹起來(lái), 而該函數(shù)就是 catch。
四、注釋
注意:PHP 內(nèi)部函數(shù)主要使用 錯(cuò)誤報(bào)告, 只有一些現(xiàn)代 面向?qū)ο?的擴(kuò)展使用異常。 不過(guò),錯(cuò)誤很容易用 ErrorException 轉(zhuǎn)化成異常。 然而,這個(gè)技術(shù)方案僅適用非 Fatal 級(jí)的錯(cuò)誤。
將錯(cuò)誤報(bào)告轉(zhuǎn)成異常:
<?php function exceptions_error_handler($severity, $message, $filename, $lineno) { throw new ErrorException($message, 0, $severity, $filename, $lineno); } set_error_handler('exceptions_error_handler'); ?>
五、示例
拋出一個(gè)異常:
<?php function inverse($x) { if (!$x) { throw new Exception('Division by zero.'); } return 1/$x; } try { echo inverse(5) . "\n"; echo inverse(0) . "\n"; } catch (Exception $e) { echo 'Caught exception: ', $e->getMessage(), "\n"; } // 繼續(xù)執(zhí)行 echo "Hello World\n"; ?>
以上示例會(huì)輸出:
0.2 Caught exception: Division by zero. Hello World
帶 finally 塊的異常處理:
<?php function inverse($x) { if (!$x) { throw new Exception('Division by zero.'); } return 1/$x; } try { echo inverse(5) . "\n"; } catch (Exception $e) { echo 'Caught exception: ', $e->getMessage(), "\n"; } finally { echo "First finally.\n"; } try { echo inverse(0) . "\n"; } catch (Exception $e) { echo 'Caught exception: ', $e->getMessage(), "\n"; } finally { echo "Second finally.\n"; } // 繼續(xù)執(zhí)行 echo "Hello World\n"; ?>
以上示例會(huì)輸出:
0.2 First finally. Caught exception: Division by zero. Second finally. Hello World
finally 和 return 相互之間的影響:
<?php function test() { try { throw new Exception('foo'); } catch (Exception $e) { return 'catch'; } finally { return 'finally'; } } echo test(); ?>
以上示例會(huì)輸出:
finally
異常嵌套:
<?php class MyException extends Exception { } class Test { public function testing() { try { try { throw new MyException('foo!'); } catch (MyException $e) { // 重新 throw throw $e; } } catch (Exception $e) { var_dump($e->getMessage()); } } } $foo = new Test; $foo->testing(); ?>
以上示例會(huì)輸出:
string(4) "foo!"
多個(gè)異常的捕獲處理:
<?php class MyException extends Exception { } class MyOtherException extends Exception { } class Test { public function testing() { try { throw new MyException(); } catch (MyException | MyOtherException $e) { var_dump(get_class($e)); } } } $foo = new Test; $foo->testing(); ?>
以上示例會(huì)輸出:
string(11) "MyException"
忽略捕獲的變量:
僅僅在 PHP 8.0.0 及以上版本有效
<?php function test() { throw new SpecificException('Oopsie'); } try { test(); } catch (SpecificException) { print "A SpecificException was thrown, but we don't care about the details."; } ?>
以表達(dá)式的形式拋出:
僅僅在 PHP 8.0.0 及以上版本有效
<?php class SpecificException extends Exception {} function test() { do_something_risky() or throw new Exception('It did not work'); } try { test(); } catch (Exception $e) { print $e->getMessage(); } ?>