PHP 异常处理

PHP 有一个和其他语言相似的异常模型。 


try

在 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 里的代码总是会执行。

如果在 try 或 catch 里遇到 return,会先执行 finally 再返回结果。 此外如果 finally 里也包含了 return 语句,将返回 finally 里的值。

全局异常处理器

通过set_exception_handler() 可以设置一个函数作为全局异常处理器,实现的效果等同于整个程序被 try-catch 包裹起来, 而该函数就是 catch。


例子

示例:抛出一个异常

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


示例:带 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
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.";
}


示例:以表达式的形式抛出

仅仅在 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();
}