PHP 有一个和其他语言相似的异常模型。在 PHP 里可以 throw 并捕获(catch)异常。为了捕获潜在的异常,代码会包含在 try 块里。每个 try 都必须至少有一个相应的 catch 或 finally 块。
如果抛出异常的函数作用域内没有 catch 块,异常会沿调用栈“向上冒泡”,直到找到匹配的 catch 块。沿途会执行所有遇到的 finally 块。在没有设置全局异常处理程序时,如果调用栈向上都没有遇到匹配的 catch,程序会抛出 fatal 错误并终止。
抛出的对象必须是 instanceof Throwable。尝试抛出其他对象会导致 PHP Fatal 错误。
PHP 8.0.0 起,throw 关键词现在开始是表达式,可用于任何表达式上下文。在此之前,它是语句,必须独占一行。
一、catch
catch 定义了处理抛出异常的方式。 catch 块定义了它能处理的异常/错误的类型,并可以选择将异常赋值到变量中。 (在 PHP 8.0.0 之前的版本中必须要赋值到变量) 如果遇到抛出对象的类型匹配了首个 catch 块的异常或错误,将会处理该对象。
可用多个 catch 捕获不同的异常类。 正常情况下(try 代码块里没有抛出异常)会在最后一个定义的 catch 后面继续执行。 catch 代码块里也可以 throw 或者重新抛出异常。 不抛出的话,会在触发的 catch 后面继续执行。
当 PHP 抛出一个异常时,将不会执行后续的代码语句,并会尝试查找首个匹配的 catch 代码块。 如果没有用 set_exception_handler() 设置异常处理函数, PHP 会在异常未被捕获时产生 Fatal 级错误,提示 “Uncaught Exception …” 消息。
从 PHP 7.1.0 起 catch 可以用竖线符(|) 指定多个异常。 如果在不同的类层次结构中,不同异常的异常需要用同样的方式处理,就特别适用这种方式。
从 PHP 8.0.0 起,捕获的异常不再强制要求指定变量名。 catch 代码块会在未指定时继续执行,只是无法访问到抛出的对象。
二、finally
finally 代码块可以放在 catch 之后,或者直接代替它。 无论是否抛出了异常,在 try 和 catch 之后、在执行后续代码之前, 放在 finally 里的代码总是会执行。
值得注意的是 finally 和 return 语句之间存在相互影响。 如果在 try 或 catch 里遇到 return,仍然会执行 finally 里的代码。 而且,遇到 return 语句时,会先执行 finally 再返回结果。 此外,如果 finally 里也包含了 return 语句,将返回 finally 里的值。
三、全局异常处理程序
当允许异常冒泡到全局作用域时,它可以被全局异常处理器捕获到。 set_exception_handler() 可以设置一个函数,在没有调用其他块时代替 catch。 在本质上,实现的效果等同于整个程序被 try-catch 包裹起来, 而该函数就是 catch。
四、注释
注意:PHP 内部函数主要使用 错误报告, 只有一些现代 面向对象 的扩展使用异常。 不过,错误很容易用 ErrorException 转化成异常。 然而,这个技术方案仅适用非 Fatal 级的错误。
将错误报告转成异常:
<?php function exceptions_error_handler($severity, $message, $filename, $lineno) { throw new ErrorException($message, 0, $severity, $filename, $lineno); } set_error_handler('exceptions_error_handler'); ?>
五、示例
抛出一个异常:
<?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"; } // 继续执行 echo "Hello World\n"; ?>
以上示例会输出:
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"; } // 继续执行 echo "Hello World\n"; ?>
以上示例会输出:
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(); ?>
以上示例会输出:
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(); ?>
以上示例会输出:
string(4) "foo!"
多个异常的捕获处理:
<?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(); ?>
以上示例会输出:
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."; } ?>
以表达式的形式抛出:
仅仅在 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(); } ?>