异常

目录

PHP 有一个和其他语言相似的异常模型。在 PHP 里可以 throw 并捕获(catch)异常。为了捕获潜在的异常,代码会包含在 try 块里。每个 try 都必须至少有一个相应的 catchfinally 块。

如果抛出异常的函数作用域内没有 catch 块,异常会沿调用栈“向上冒泡”,直到找到匹配的 catch 块。沿途会执行所有遇到的 finally 块。在没有设置全局异常处理程序时,如果调用栈向上都没有遇到匹配的 catch,程序会抛出 fatal 错误并终止。

抛出的对象必须是 instanceof Throwable。尝试抛出其他对象会导致 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

注释

注意:

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

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

// 继续执行
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";
}

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

以上例程会输出:

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

示例 #4 finallyreturn 相互之间的影响

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

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

try {
test();
} catch (
SpecificException) {
print
"A SpecificException was thrown, but we don't care about the details.";
}
?>

示例 #8 以表达式的形式抛出

仅仅在 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();
}
?>
add a note

User Contributed Notes 20 notes

up
23
Johan
12 years ago
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());
}
?>
up
9
michael dot ochs at gmx dot net
16 years ago
Actually it isn't possible to do:
<?php
someFunction
() OR throw new Exception();
?>

This leads to a T_THROW Syntax Error. If you want to use this kind of exceptions, you can do the following:

<?php
function throwException($message = null,$code = null) {
    throw new
Exception($message,$code);
}

someFunction() OR throwException();
?>
up
4
sander at rotorsolutions dot nl
10 years ago
Just an example why finally blocks are usefull (5.5)

<?php

//without catch
function example() {
  try {
   
//do something that throws an exeption
 
}
  finally {
   
//this code will be executed even when the exception is executed
 
}
}

function
example2() {
  try {
    
//open sql connection check user as example
    
if(condition) {
        return
false;
     }
  }
  finally {
   
//close the sql connection, this will be executed even if the return is called.
 
}
}

?>
up
5
ask at nilpo dot com
14 years ago
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}
up
3
alex dowgailenko [at] g mail . com
12 years ago
If you use the set_error_handler() to throw exceptions of errors, you may encounter issues with __autoload() functionality saying that your class doesn't exist and that's it.

If you do this:

<?php

class MyException extends Exception
{
}

class
Tester
{
    public function
foobar()
    {
        try
        {
           
$this->helloWorld();
        } catch (
MyException $e) {
            throw new
Exception('Problem in foobar',0,$e);
        }
    }
   
    protected function
helloWorld()
    {
        throw new
MyException('Problem in helloWorld()');
    }
}

$tester = new Tester;
try
{
   
$tester->foobar();
} catch (
Exception $e) {
    echo
$e->getTraceAsString();
}
?>

The trace will only show $tester->foobar() and not the call made to $tester->helloWorld().

In other words, if you pass a previous exception to a new one, the previous exception's stack trace is taken into account in the new exception.
up
3
Shot (Piotr Szotkowski)
15 years ago
‘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).
up
1
jon at hackcraft dot net
17 years ago
Further to dexen at google dot me dot up with "use destructors to perform a cleanup in case of exception". The fact that PHP5 has destructors, exception handling, and predictable garbage collection (if there's a single reference in scope and the scope is left then the destructor is called immediately) allows for the use of the RAII idiom.

http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization and my own http://www.hackcraft.net/RAII/ describe this.
up
1
Edu
10 years ago
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
up
1
Sawsan
12 years ago
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/>";
   }

?>
up
1
zmunoz at gmail dot com
13 years ago
When catching an exception inside a namespace it is important that you escape to the global space:

<?php
namespace SomeNamespace;

class
SomeClass {

  function
SomeFunction() {
   try {
    throw new
Exception('Some Error Message');
   } catch (
\Exception $e) {
   
var_dump($e->getMessage());
   }
  }

}
?>
up
1
jazfresh at hotmail.com
17 years ago
Sometimes you want a single catch() to catch multiple types of Exception. In a language like Python, you can specify multiple types in a catch(), but in PHP you can only specify one. This can be annoying when you want handle many different Exceptions with the same catch() block.

However, you can replicate the functionality somewhat, because catch(<classname> $var) will match the given <classname> *or any of it's sub-classes*.

For example:

<?php
class DisplayException extends Exception {};
class
FileException extends Exception {};
class
AccessControl extends FileException {}; // Sub-class of FileException
class IOError extends FileException {}; // Sub-class of FileException

try {
  if(!
is_readable($somefile))
     throw new
IOError("File is not readable!");
  if(!
user_has_access_to_file($someuser, $somefile))
     throw new
AccessControl("Permission denied!");
  if(!
display_file($somefile))
     throw new
DisplayException("Couldn't display file!");

} catch (
FileException $e) {
 
// This block will catch FileException, AccessControl or IOError exceptions, but not Exceptions or DisplayExceptions.
 
echo "File error: ".$e->getMessage();
  exit(
1);
}
?>

Corollary: If you want to catch *any* exception, no matter what the type, just use "catch(Exception $var)", because all exceptions are sub-classes of the built-in Exception.
up
0
jim at anderos dot com
10 years ago
If you are using a namespace, you must indicate the global namespace when using Exceptions.
<?php
namespace alpha;
function
foo(){
    throw new
\Exception("Something is wrong!");
   
// throw new Exception(""); will fail
}

try {
   
foo();
} catch(
\Exception $e ) {
   
// catch( Exception $e ) will give no warning, but will not catch Exception
   
echo "ERROR: $e";
}

?>
up
0
sander at rotorsolutions dot nl
10 years ago
Just an example why finally blocks are usefull (5.5)

<?php

//without catch
function example() {
  try {
   
//do something that throws an exeption
 
}
  finally {
   
//this code will be executed even when the exception is executed
 
}
}

function
example2() {
  try {
    
//open sql connection check user as example
    
if(condition) {
        return
false;
     }
  }
  finally {
   
//close the sql connection, this will be executed even if the return is called.
 
}
}
up
0
sander at rotorsolutions dot nl
10 years ago
Just an example why finally blocks are usefull (5.5)

<?php

//without catch
function example() {
  try {
   
//do something that throws an exeption
 
}
  finally {
   
//this code will be executed even when the exception is executed
 
}
}

function
example2() {
  try {
    
//open sql connection check user as example
    
if(condition) {
        return
false;
     }
  }
  finally {
   
//close the sql connection, this will be executed even if the return is called.
 
}
}

?>
up
-1
cyrus+php at boadway dot ca
10 years ago
There's some inconsistent behaviour associated with PHP 5.5.3's finally and return statements. If a method returns a variable in a try block (e.g. return $foo;), and finally modifies that variable, the /modified/ value is returned. However, if the try block has a return that has to be evaluated in-line (e.g. return $foo+0;), finally's changes to $foo will /not/ affect the return value.

[code]
function returnVariable(){
    $foo = 1;
    try{
        return $foo;
    } finally {
        $foo++;
    }
}

function returnVariablePlusZero(){
    $foo = 1;
    try{
        return $foo + 0;
    } finally {
        $foo++;
    }
}

$test1 = returnVariable(); // returns 2, not the correct value of 1.
$test2 = returnVariablePlusZero(); // returns correct value of 1, but inconsistent with $test1.
[/code]

It looks like it's trying to be efficient by not allocating additional memory for the return value when it thinks it doesn't have to, but the spec is that finally is run after try is completed execution, and that includes the evaluation of the return expression.

One could argue (weakly) that the first method should be the correct result, but at least the two methods should be consistent.
up
0
chugadie dot geo at yahoo dot com
16 years ago
@webmaster at asylum-et dot com

What Mo is describing is bug 44053 (http://bugs.php.net/bug.php?id=44053) in which exceptions cannot be caught if you are using a custom error handler to catch warnings, notices, etc.
up
0
omnibus at omnibus dot edu dot pl
16 years ago
Just to be more precise in what Frank found:
Catch the exceptions always in order from the bottom to the top of the Exception and subclasses class hierarchy. If you have class MyException extending Exception and class My2Exception extending MyException always catch My2Exception before MyException.

Hope this helps
up
0
hartym dot dont dot like dot spam at gmail dot com
16 years ago
@serenity: of course you need to throw exception within the try block, catch will not watch fatal errors, nor less important errors but only exceptions that are instanceof the exception type you're giving. Of course by within the try block, i mean within every functions call happening in try block.

For example, to nicely handle old mysql errors, you can do something like this:

<?php
try
{
 
$connection = mysql_connect(...);
  if (
$connection === false)
  {
    throw new
Exception('Cannot connect do mysql');
  }

  
/* ... do whatever you need with database, that may mail and throw exceptions too ... */

  
mysql_close($connection);
}
catch (
Exception $e)
{
  
/* ... add logging stuff there if you need ... */

 
echo "This page cannot be displayed";
}

?>

By doing so, you're aiming at the don't repeat yourself (D.R.Y) concept, by managing error handling at only one place for the whole.
up
0
fjoggen at gmail dot com
17 years ago
This code will turn php errors into exceptions:

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

set_error_handler('exceptions_error_handler');
?>

However since <?php set_error_handler()?> doesn't work with fatal errors, you will not be able to throw them as Exceptions.
up
-3
mike at clove dot com
14 years ago
The PHP documentation has gone from very useful to hideously obstructive.

The people who are rearranging the doc into little, tiny chunks which are hyperlinked all over the place obviously never write code.

I just spent 10 minutes trying to find the name of an IO Exception so I can use it in some code I'm writing.

Old Doc: I would go to the index, click on Exceptions and then scroll down the page (or do a find on IO) and there it would be. 10 seconds tops.

New Doc: Go to the index click on Predefined Exceptions
Click on Exception - find description of Exception Object - info not there
Back Button
Click on Error Exception - find description of Generic ErrorExeption object
Back Button
Click on SPL Exceptions (what the hell is this? - something new?)
Look at Table of contents: 13 Exception Categories - none of which
  looks like an IOException
Click on Predefined Exceptions in the See Also -
   Back to Previous Useless Page

First You completely screw up the Perl Regular Expression page by chopping it into tiny, obscure chunks and now you destroy the exception documentation.

PLEASE put it back the way it was.

Or get somebody who actually uses this stuff like a handbook while writing code to fix it

Or shoot somebody.

Incredibly frustrated and thinking of rewriting everything in Python,
Mike Howard <mike at clove dot com>
To Top