异常

目录

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

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

抛出的对象必须是instanceof Throwable。尝试抛出不是该类的对象会导致 PHP 致命错误。

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

catch

catch 块定义了如何响应抛出的异常。一个catch 块定义了它可以处理的一种或多种异常或错误类型,并可以选择一个变量来赋值给异常。(在 PHP 8.0.0 之前,变量是必需的。)抛出的异常或错误遇到的第一个catch 块,只要其类型与抛出对象的类型匹配,就会处理该对象。

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

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

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

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

finally

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

一个值得注意的交互作用是finally 块和return 语句之间的交互作用。如果在trycatch 块中遇到return 语句,则finally 块仍然会执行。此外,return 语句在遇到时会进行评估,但结果将在finally 块执行之后返回。此外,如果finally 块也包含return 语句,则将返回来自finally 块的值。

全局异常处理程序

如果异常被允许冒泡到全局作用域,则如果设置了全局异常处理程序,它可能会被捕获。该set_exception_handler() 函数可以设置一个函数,如果未调用其他块,则该函数将代替catch 块被调用。其效果与用该函数作为catch 将整个程序包装在try-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('Division by zero.');
}
return
1/$x;
}

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

// Continue execution
echo "Hello World\n";
?>

上面的例子将输出

0.2
Caught exception: Division by zero.
Hello World

示例 #3 使用 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";
}

// Continue execution
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) {
// rethrow it
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
"A SpecificException was thrown, but we don't care about the details.";
}
?>

示例 #8 将 throw 作为表达式

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

<?php

function test() {
do_something_risky() or throw new Exception('It did not work');
}

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

用户贡献注释 13 notes

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

<?php
interface 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);
}

abstract class
CustomException extends Exception implements IException
{
protected
$message = 'Unknown exception'; // 异常消息
private $string; // 未知
protected $code = 0; // 用户定义的异常代码
protected $file; // 异常发生的源文件名
protected $line; // 异常发生的源行
private $trace; // 未知

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

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

现在您可以一行代码创建新的异常

<?php
class TestException extends CustomException {}
?>

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

<?php
function exceptionTest()
{
try {
throw new
TestException();
}
catch (
TestException $e) {
echo
"Caught TestException ('{$e->getMessage()}')\n{$e}\n";
}
catch (
Exception $e) {
echo
"Caught Exception ('{$e->getMessage()}')\n{$e}\n";
}
}

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

这是一个示例输出

Caught TestException ('Unknown TestException')
TestException 'Unknown TestException' in 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}
Johan
13 years ago
在整个页面上使用自定义错误处理可以避免用户看到半渲染的页面

<?php
ob_start
();
try {
/* 包含所有页面逻辑
如果需要则抛出错误*/
...
} catch (
Exception $e) {
ob_end_clean();
displayErrorPage($e->getMessage());
}
?>
tianyiw at vip dot qq dot com
11 months ago
易于理解 `finally`。
<?php
try {
try {
echo
"before\n";
1 / 0;
echo
"after\n";
} finally {
echo
"finally\n";
}
} catch (
\Throwable) {
echo
"exception\n";
}
?>
# Print
before
finally
exception
jlherren
6 months ago
如其他地方所述,从 `finally` 块抛出异常将替换先前抛出的异常。但原始异常会神奇地从新异常的 `getPrevious()` 中获取。

<?php
try {
try {
throw new
RuntimeException('Exception A');
} finally {
throw new
RuntimeException('Exception B');
}
}
catch (
Throwable $exception) {
echo
$exception->getMessage(), "\n";
// 'previous' 会神奇地出现!
echo $exception->getPrevious()->getMessage(), "\n";
}
?>

将打印

Exception B
Exception A
daviddlowe dot flimm at gmail dot com
6 years ago
从 PHP 7 开始,Exception 和 Error 类都实现了 Throwable 接口。这意味着,如果你想捕获 Error 实例和 Exception 实例,你应该捕获 Throwable 对象,像这样

<?php

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

?>
Shot (Piotr Szotkowski)
15 年前
‘正常执行(当 try 块中没有抛出异常时,*或当没有出现与抛出异常的类匹配的 catch 块时*)将在最后一个按顺序定义的 catch 块之后继续。’

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

这两句话似乎在“当没有出现与抛出异常的类匹配的 catch 块时”会发生什么方面有些矛盾(第二句话实际上是正确的)。
christof+php[AT]insypro.com
7 years ago
如果你的 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();
}
?>
Edu
11 years ago
"finally" 块可以更改 catch 块抛出的异常。

<?php
try{
try {
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
mlaopane at gmail dot com
6 years ago
<?php

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

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

function
manager()
{
employee();
}

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

boss(); // 输出: "我只是个员工!"
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 block<br />";
throw new
Exception("test");
} catch (
Exception $ex) {
echo
"catch block<br />";
} finally {
echo
"finally block<br />";
}

// try block
// catch block
// finally block
?>

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

// try block
// catch block
?>
Tom Polomsk
9 年前
与文档相反,在 PHP 5.5 及更高版本中,可以使用仅包含 try-finally 块而没有 catch 块。
Sawsan
12 年前
以下是如何重新抛出异常以及使用 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