异常
目录
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();
}
?>

User Contributed Notes 12 notes
If you intend on creating a lot of custom exceptions, you may find this code useful. I've created an interface and an abstract exception class that ensures that all parts of the built-in Exception class are preserved in child classes. It also properly pushes all information back to the parent constructor ensuring that nothing is lost. This allows you to quickly create new exceptions on the fly. It also overrides the default __toString method with a more thorough one.
<?php
interface IException
{
/* Protected methods inherited from Exception class */
public function getMessage(); // Exception message
public function getCode(); // User-defined Exception code
public function getFile(); // Source filename
public function getLine(); // Source line
public function getTrace(); // An array of the backtrace()
public function getTraceAsString(); // Formated string of trace
/* Overrideable methods inherited from Exception class */
public function __toString(); // formated string for display
public function __construct($message = null, $code = 0);
}
abstract class CustomException extends Exception implements IException
{
protected $message = 'Unknown exception'; // Exception message
private $string; // Unknown
protected $code = 0; // User-defined exception code
protected $file; // Source filename of exception
protected $line; // Source line of exception
private $trace; // Unknown
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()}";
}
}
?>
Now you can create new exceptions in one line:
<?php
class TestException extends CustomException {}
?>
Here's a test that shows that all information is properly preserved throughout the backtrace.
<?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>';
?>
Here's a sample output:
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}
Custom error handling on entire pages can avoid half rendered pages for the users:
<?php
ob_start();
try {
/*contains all page logic
and throws error if needed*/
...
} catch (Exception $e) {
ob_end_clean();
displayErrorPage($e->getMessage());
}
?>
In case your E_WARNING type of errors aren't catchable with try/catch you can change them to another type of error like this:
<?php
set_error_handler(function($errno, $errstr, $errfile, $errline){
if($errno === E_WARNING){
// make it more serious than a warning so it can be caught
trigger_error($errstr, E_ERROR);
return true;
} else {
// fallback to default php error handler
return false;
}
});
try {
// code that might result in a E_WARNING
} catch(Exception $e){
// code to handle the E_WARNING (it's actually changed to E_ERROR at this point)
} finally {
restore_error_handler();
}
?>
‘Normal execution (when no exception is thrown within the try block, *or when a catch matching the thrown exception’s class is not present*) will continue after that last catch block defined in sequence.’
‘If an exception is not caught, a PHP Fatal Error will be issued with an “Uncaught Exception …” message, unless a handler has been defined with set_exception_handler().’
These two sentences seem a bit contradicting about what happens ‘when a catch matching the thrown exception’s class is not present’ (and the second sentence is actually correct).
The "finally" block can change the exception that has been throw by the catch block.
<?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";
}
?>
The output is:
Hello catch in
Hello finally
Bye catch out
#3 is not a good example. inverse("0a") would not be caught since (bool) "0a" returns true, yet 1/"0a" casts the string to integer zero and attempts to perform the calculation.
Starting in PHP 7, the classes Exception and Error both implement the Throwable interface. This means, if you want to catch both Error instances and Exception instances, you should catch Throwable objects, like this:
<?php
try {
throw new Error( "foobar" );
// or:
// throw new Exception( "foobar" );
}
catch (Throwable $e) {
var_export( $e );
}
?>
<?php
/**
* You can catch exceptions thrown in a deep level function
*/
function employee()
{
throw new \Exception("I am just an employee !");
}
function manager()
{
employee();
}
function boss()
{
try {
manager();
} catch (\Exception $e) {
echo $e->getMessage();
}
}
boss(); // output: "I am just an employee !"
When using finally keep in mind that when a exit/die statement is used in the catch block it will NOT go through the finally block.
<?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
?>
Contrary to the documentation it is possible in PHP 5.5 and higher use only try-finally blocks without any catch block.
the following is an example of a re-thrown exception and the using of getPrevious function:
<?php
$name = "Name";
//check if the name contains only letters, and does not contain the word name
try
{
try
{
if (preg_match('/[^a-z]/i', $name))
{
throw new Exception("$name contains character other than a-z A-Z");
}
if(strpos(strtolower($name), 'name') !== FALSE)
{
throw new Exception("$name contains the word name");
}
echo "The Name is valid";
}
catch(Exception $e)
{
throw new Exception("insert name again",0,$e);
}
}
catch (Exception $e)
{
if ($e->getPrevious())
{
echo "The Previous Exception is: ".$e->getPrevious()->getMessage()."<br/>";
}
echo "The Exception is: ".$e->getMessage()."<br/>";
}
?>
I would like to emphasise that you can not rethrow an Exception inside a catch-block and expect that the next catch-block will handle it.
<?php
try {
throw new RuntimeException('error');
} catch (RuntimeException $e) {
throw $e;
} catch (Exception $e) {
// this will not be executed[
}
?>