异常

Table of Contents

PHP 有一个和其他语言相似的异常模型。 在 PHP 里可以 throwcatch 异常。 为了捕获潜在的异常,可以将代码包含在 try 块里。 每个 try 都必须有一个相应的 catchfinally 代码块。

如果抛出异常的函数范围内没有 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 之后,或者直接代替它。 无论是否抛出了异常,在 trycatch 之后、在执行后续代码之前, 放在 finally 里的代码总是会执行。

值得注意的是 finallyreturn 语句之间存在相互影响。 如果在 trycatch 里遇到 return,仍然会执行 finally 里的代码。 而且,遇到 return 语句时,会先执行 finally 再返回结果。 此外,如果 finally 里也包含了 return 语句,将返回 finally 里的值。

全局异常处理器

当允许异常冒泡到全局范围时,它可以被全局异常处理器捕获到。 set_exception_handler() 可以设置一个函数,在没有调用其他块时代替 catch。 在本质上,实现的效果等同于整个程序被 try-catch 包裹起来, 而该函数就是 catch

注释

Note:

PHP 内部函数主要使用 错误报告, 只有一些现代 面向对象 的扩展使用异常。 不过,错误很容易用 ErrorException 转化成异常。 然而,这个技术方案仅适用非 Fatal 级的错误。

Example #3 将错误报告转成异常

<?php
function exceptions_error_handler($severity$message$filename$lineno) {
    throw new 
ErrorException($message0$severity$filename$lineno);
}

set_error_handler('exceptions_error_handler');
?>

Tip

PHP 标准库(SPL) 提供了大量的 标准内置异常

范例

Example #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";
}

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

以上例程会输出:

0.2
Caught exception: Division by zero.
Hello World

Example #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

Example #6 finallyreturn 相互之间的影响

<?php

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

echo 
test();
?>

以上例程会输出:

finally

Example #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!"

Example #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"

Example #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.";
}
?>

Example #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

Daan 25-Jun-2021 02:54
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[
}
?>
mlaopane at gmail dot com 24-Feb-2018 09:49
<?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 !"
daviddlowe dot flimm at gmail dot com 22-Sep-2017 01:13
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 );
}

?>
christof+php[AT]insypro.com 15-Jun-2017 10:12
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();
    }
?>
Simo 04-Apr-2015 02:47
#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.
Tom Polomsk 04-Dec-2014 01:45
Contrary to the documentation it is possible in PHP 5.5 and higher use only try-finally blocks without any catch block.
telefoontoestel at nospam dot org 18-Jun-2014 12:34
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
?>
Edu 24-Jun-2013 06:13
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
Sawsan 28-Nov-2011 09:36
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/>";
   }

 
?>
Johan 05-May-2011 01:18
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());
}
?>
ask at nilpo dot com 27-May-2009 12:19
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}
Shot (Piotr Szotkowski) 21-Oct-2008 12:13
'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).