PHP 有一个和其他语言相似的异常模型。
在 PHP 里可以 throw
并 catch
异常。
为了捕获潜在的异常,可以将代码包含在 try
块里。
每个 try
都必须有一个相应的
catch
或 finally
代码块。
如果抛出异常的函数范围内没有 catch
块,异常会沿调用栈“向上冒泡”,
直到找到匹配的 catch
块。
沿途会执行所有遇到的 finally
块。
在没有设置全局异常处理程序(exception handler)时,
如果调用栈向上都没有遇到匹配的 catch
,程序会抛出 fatal 错误并终止执行。
抛出的对象必须是 Exception 自身或 Exception的子类。 抛出其他对象会导致 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 级的错误。
示例 #3 将错误报告转成异常
<?php
function exceptions_error_handler($severity, $message, $filename, $lineno) {
throw new ErrorException($message, 0, $severity, $filename, $lineno);
}
set_error_handler('exceptions_error_handler');
?>
PHP 标准库(SPL) 提供了大量的 标准内置异常。
示例 #4 抛出一个异常
<?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
示例 #5 带 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
示例 #6 finally
和 return
相互之间的影响
<?php
function test() {
try {
throw new Exception('foo');
} catch (Exception $e) {
return 'catch';
} finally {
return 'finally';
}
}
echo test();
?>
以上例程会输出:
finally
示例 #7 异常嵌套
<?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!"
示例 #8 多个异常的捕获处理
<?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"
示例 #9 忽略捕获的变量
仅仅在 PHP 8.0.0 及以上版本有效
<?php
class SpecificException extends Exception {}
function test() {
throw new SpecificException('Oopsie');
}
try {
test();
} catch (SpecificException) {
print "A SpecificException was thrown, but we don't care about the details.";
}
?>
示例 #10 以表达式的形式抛出
仅仅在 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();
}
?>