PHP Conference Japan 2024

异常

目录

PHP 的异常模型类似于其他编程语言。异常可以被throw抛出,并在 PHP 中被捕获(“catch”)。代码可以包含在一个try块中,以便于捕获潜在的异常。每个try块必须至少对应一个catchfinally块。

如果抛出一个异常,并且其当前函数作用域没有catch块,则该异常将“冒泡”到调用栈中的调用函数,直到找到一个匹配的catch块。它在此过程中遇到的所有finally块都将被执行。如果调用栈一直展开到全局作用域,而没有遇到匹配的catch块,则程序将终止并出现致命错误,除非已设置全局异常处理程序。

抛出的对象必须是Throwableinstanceof。尝试抛出一个不是Throwable的对象将导致 PHP 致命错误。

从 PHP 8.0.0 开始,throw关键字是一个表达式,可以在任何表达式上下文中使用。在以前的版本中,它是一个语句,必须单独占用一行。

catch

一个catch块定义了如何响应抛出的异常。一个catch块定义了它可以处理的一种或多种异常或错误类型,以及可选地将异常分配到的变量。(在 PHP 8.0.0 之前,变量是必需的。)抛出的异常或错误遇到的第一个匹配抛出对象类型的catch块将处理该对象。

可以使用多个catch块来捕获不同类别的异常。正常执行(当try块中没有抛出异常时)将在按顺序定义的最后一个catch块之后继续。异常可以在catch块中被throw(或重新抛出)。如果不是,则执行将在触发catch块之后继续。

当抛出异常时,语句后面的代码将不会执行,PHP 将尝试找到第一个匹配的catch块。如果未捕获异常,则将发出 PHP 致命错误,并显示“Uncaught Exception ...”消息,除非已使用set_exception_handler()定义了处理程序。

从 PHP 7.1.0 开始,catch块可以使用管道 (|) 字符指定多个异常。当来自不同类层次结构的不同异常以相同的方式处理时,这很有用。

从 PHP 8.0.0 开始,捕获异常的变量名是可选的。如果未指定,catch块仍将执行,但无法访问抛出的对象。

finally

catch块之后或代替catch块还可以指定finally块。无论是否抛出异常,finally块中的代码都将在trycatch块之后以及在恢复正常执行之前始终执行。

finally块和return语句之间有一个值得注意的交互。如果在trycatch块内遇到return语句,则finally块仍将执行。此外,当遇到return语句时,会对其进行评估,但在执行finally块之后才会返回结果。此外,如果finally块也包含return语句,则将返回finally块中的值。

全局异常处理程序

如果允许异常冒泡到全局作用域,则如果已设置,它可能会被全局异常处理程序捕获。set_exception_handler()函数可以设置一个函数,如果未调用任何其他块,则该函数将代替catch块被调用。其效果与将整个程序包装在一个带有该函数作为catchtry-catch块中基本相同。

备注

注意:

内部 PHP 函数主要使用错误报告,只有现代的面向对象扩展使用异常。但是,可以使用ErrorException轻松地将错误转换为异常。但是,此技术仅适用于非致命错误。

示例 #1 将错误报告转换为异常

<?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)提供了大量的内置异常

示例

示例 #2 抛出异常

<?php
function inverse($x) {
if (!
$x) {
throw new
Exception('除以零。');
}
return
1/$x;
}

try {
echo
inverse(5) . "\n";
echo
inverse(0) . "\n";
} catch (
Exception $e) {
echo
'捕获异常: ', $e->getMessage(), "\n";
}

// 继续执行
echo "Hello World\n";
?>

以上示例将输出

0.2
Caught exception: Division by zero.
Hello World

示例 #3 使用 finally 块的异常处理

<?php
function inverse($x) {
if (!
$x) {
throw new
Exception('除以零。');
}
return
1/$x;
}

try {
echo
inverse(5) . "\n";
} catch (
Exception $e) {
echo
'捕获异常: ', $e->getMessage(), "\n";
} finally {
echo
"第一个 finally。\n";
}

try {
echo
inverse(0) . "\n";
} catch (
Exception $e) {
echo
'捕获异常: ', $e->getMessage(), "\n";
} finally {
echo
"第二个 finally。\n";
}

// 继续执行
echo "Hello World\n";
?>

以上示例将输出

0.2
First finally.
Caught exception: Division by zero.
Second finally.
Hello World

示例 #4 finally 块与 return 之间的交互

<?php

function test() {
try {
throw new
Exception('foo');
} catch (
Exception $e) {
return
'catch';
} finally {
return
'finally';
}
}

echo
test();
?>

以上示例将输出

finally

示例 #5 嵌套异常

<?php

class MyException extends Exception { }

class
Test {
public function
testing() {
try {
try {
throw new
MyException('foo!');
} catch (
MyException $e) {
// 重新抛出
throw $e;
}
} catch (
Exception $e) {
var_dump($e->getMessage());
}
}
}

$foo = new Test;
$foo->testing();

?>

以上示例将输出

string(4) "foo!"

示例 #6 多重捕获异常处理

<?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"

示例 #7 省略捕获变量

仅在 PHP 8.0.0 及更高版本中允许。

<?php

class SpecificException extends Exception {}

function
test() {
throw new
SpecificException('Oopsie');
}

try {
test();
} catch (
SpecificException) {
print
"抛出了 SpecificException,但我们不关心细节。";
}
?>

示例 #8 将 throw 用作表达式

仅在 PHP 8.0.0 及更高版本中允许。

<?php

function test() {
do_something_risky() or throw new Exception('它没有工作');
}

try {
test();
} catch (
Exception $e) {
print
$e->getMessage();
}
?>
添加注释

用户贡献的注释 13 条注释

ask at nilpo dot com
15 年前
如果您打算创建许多自定义异常,您可能会发现此代码很有用。我创建了一个接口和一个抽象异常类,以确保内置 Exception 类的所有部分都保留在子类中。它还将所有信息正确地推送到父构造函数,以确保没有任何信息丢失。这允许您快速创建新的异常。它还使用更全面的方法覆盖了默认的 __toString 方法。



<?php
接口 IException
{
/* 从 Exception 类继承的受保护方法 */
public function getMessage(); // 异常消息
public function getCode(); // 用户自定义的异常代码
public function getFile(); // 源文件名
public function getLine(); // 源代码行
public function getTrace(); // 回溯的数组
public function getTraceAsString(); // 格式化的回溯字符串

/* 从 Exception 类继承的可重写方法 */
public function __toString(); // 用于显示的格式化字符串
public function __construct($message = null, $code = 0);
}

抽象类
CustomException 扩展 Exception 并实现 IException
{
protected
$message = '未知异常'; // 异常消息
private $string; // 未知
protected $code = 0; // 用户自定义的异常代码
protected $file; // 异常的源文件名
protected $line; // 异常的源代码行
private $trace; // 未知

public function __construct($message = null, $code = 0)
{
if (!
$message) {
throw new
$this('未知 '. get_class($this));
}
parent::__construct($message, $code);
}

public function
__toString()
{
return
get_class($this) . " '{$this->message}' 在 {$this->file}{$this->line})\n"
. "{$this->getTraceAsString()}";
}
}
?>

现在你可以一行创建新的异常

<?php
TestException 扩展 CustomException {}
?>

这是一个测试,它展示了所有信息如何在整个回溯过程中正确保留。

<?php
函数 exceptionTest()
{
try {
throw new
TestException();
}
catch (
TestException $e) {
echo
"捕获到 TestException ('{$e->getMessage()}')\n{$e}\n";
}
catch (
Exception $e) {
echo
"捕获到 Exception ('{$e->getMessage()}')\n{$e}\n";
}
}

echo
'<pre>' . exceptionTest() . '</pre>';
?>

这是一个示例输出

捕获到 TestException ('未知 TestException')
TestException '未知 TestException' 在 C:\xampp\htdocs\CustomException\CustomException.php(31)
#0 C:\xampp\htdocs\CustomException\ExceptionTest.php(19): CustomException->__construct()
#1 C:\xampp\htdocs\CustomException\ExceptionTest.php(43): exceptionTest()
#2 {main}
tianyiw at vip dot qq dot com
1 年前
易于理解 `finally`。
<?php
尝试 {
尝试 {
echo
"之前\n";
1 / 0;
echo
"之后\n";
} finally {
echo
"最终\n";
}
} catch (
\Throwable) {
echo
"异常\n";
}
?>
# 打印
之前
finally
异常
Johan
13 年前
对整个页面的自定义错误处理可以避免用户看到页面渲染一半的情况

<?php
ob_start
();
尝试 {
/*包含所有页面逻辑
并在需要时抛出错误*/
...
} catch (
Exception $e) {
ob_end_clean();
displayErrorPage($e->getMessage());
}
?>
jlherren
10 个月前
如其他地方所述,从 `finally` 块抛出异常将替换先前抛出的异常。但原始异常可以通过新异常的 `getPrevious()` 魔术般地获取。

<?php
尝试 {
尝试 {
throw new
RuntimeException('异常 A');
} finally {
throw new
RuntimeException('异常 B');
}
}
catch (
Throwable $exception) {
echo
$exception->getMessage(), "\n";
// 'previous' 可以魔术般地获取!
echo $exception->getPrevious()->getMessage(), "\n";
}
?>

将打印

异常 B
异常 A
Shot (Piotr Szotkowski)
16 年前
“正常执行(当 try 块中没有抛出异常时,*或当不存在与抛出异常的类匹配的 catch 时*)将在定义的最后一个 catch 块之后继续。”

“如果异常没有被捕获,PHP 将发出一个致命错误,并显示“未捕获的异常……”消息,除非使用 set_exception_handler() 定义了一个处理程序。”

这两句话关于“当不存在与抛出异常的类匹配的 catch 时”会发生什么情况似乎有点矛盾(而第二句话实际上是正确的)。
christof+php[AT]insypro.com
7 年前
如果你的 E_WARNING 类型的错误无法用 try/catch 捕获,你可以像这样将它们更改为其他类型的错误

<?php
set_error_handler
(function($errno, $errstr, $errfile, $errline){
if(
$errno === E_WARNING){
// 使其比警告更严重,以便可以捕获
trigger_error($errstr, E_ERROR);
return
true;
} else {
// 回退到默认的 php 错误处理程序
return false;
}
});

try {
// 可能导致 E_WARNING 的代码
} catch(Exception $e){
// 处理 E_WARNING 的代码(此时它实际上已更改为 E_ERROR)
} finally {
restore_error_handler();
}
?>
daviddlowe dot flimm at gmail dot com
7 年前
从 PHP 7 开始,Exception 和 Error 类都实现了 Throwable 接口。这意味着,如果要同时捕获 Error 实例和 Exception 实例,应该捕获 Throwable 对象,如下所示

<?php

尝试 {
throw new
Error( "foobar" );
// 或:
// throw new Exception( "foobar" );
}
catch (
Throwable $e) {
var_export( $e );
}

?>
Edu
11 年前
"finally" 块可以更改 catch 块抛出的异常。

<?php
尝试{
尝试 {
throw new
\Exception("Hello");
} catch(
\Exception $e) {
echo
$e->getMessage()." catch in\n";
throw
$e;
} finally {
echo
$e->getMessage()." finally \n";
throw new
\Exception("Bye");
}
} catch (
\Exception $e) {
echo
$e->getMessage()." catch out\n";
}
?>

输出为

Hello catch in
Hello finally
Bye catch out
Simo
9 年前
#3 不是一个好的例子。inverse("0a") 不会被捕获,因为 (bool) "0a" 返回 true,但 1/"0a" 会将字符串转换为整数零并尝试执行计算。
telefoontoestel at nospam dot org
10 年前
使用 finally 时请记住,当在 catch 块中使用 exit/die 语句时,它将不会执行 finally 块。



<?php
try {
echo
"try 代码块<br />";
throw new
Exception("test");
} catch (
Exception $ex) {
echo
"catch 代码块<br />";
} finally {
echo
"finally 代码块<br />";
}

// try 代码块
// catch 代码块
// finally 代码块
?>

<?php
try {
echo
"try 代码块<br />";
throw new
Exception("test");
} catch (
Exception $ex) {
echo
"catch 代码块<br />";
exit(
1);
} finally {
echo
"finally 代码块<br />";
}

// try 代码块
// catch 代码块
?>
mlaopane at gmail dot com
6 年前
<?php

/**
* 可以在深层函数中捕获抛出的异常
*/

function employee()
{
throw new
\Exception("我只是个员工!");
}

function
manager()
{
employee();
}

function
boss()
{
try {
manager();
} catch (
\Exception $e) {
echo
$e->getMessage();
}
}

boss(); // 输出:"我只是个员工!"
Tom Polomsk
10 年前
与文档相反,在 PHP 5.5 及更高版本中,可以使用仅包含 try-finally 代码块而没有任何 catch 代码块的结构。
Sawsan
13 年前
以下是一个重新抛出异常并使用 getPrevious 函数的示例

<?php

$name
= "Name";

// 检查名称是否仅包含字母,并且不包含单词 name

try
{
try
{
if (
preg_match('/[^a-z]/i', $name))
{
throw new
Exception("$name 包含除 a-z A-Z 之外的字符");
}
if(
strpos(strtolower($name), 'name') !== FALSE)
{
throw new
Exception("$name 包含单词 name");
}
echo
"名称有效";
}
catch(
Exception $e)
{
throw new
Exception("请再次输入名称",0,$e);
}
}

catch (
Exception $e)
{
if (
$e->getPrevious())
{
echo
"之前的异常是:".$e->getPrevious()->getMessage()."<br/>";
}
echo
"异常是:".$e->getMessage()."<br/>";
}

?>
To Top