语言参考
在线手册:中文  英文

异常处理

Table of Contents

PHP 5 添加了类似于其它语言的异常处理模块。在 PHP 代码中所产生的异常可被 throw 语句抛出并被 catch 语句捕获。需要进行异常处理的代码都必须放入 try 代码块内,以便捕获可能存在的异常。每一个 try 至少要有一个与之对应的 catch。使用多个 catch 可以捕获不同的类所产生的异常。当 try 代码块不再抛出异常或者找不到 catch 能匹配所抛出的异常时,PHP 代码就会在跳转到最后一个 catch 的后面继续执行。当然,PHP 允许在 catch 代码块内再次抛出(throw)异常。

当一个异常被抛出时,其后(译者注:指抛出异常时所在的代码块)的代码将不会继续执行,而 PHP 就会尝试查找第一个能与之匹配的 catch。如果一个异常没有被捕获,而且又没用使用 set_exception_handler() 作相应的处理的话,那么 PHP 将会产生一个严重的错误,并且输出 Uncaught Exception ... (未捕获异常)的提示信息。

Note:

PHP 内部函数主要使用错误报告, 只有现代面向对象的扩展才使用异常。但错误可以很容易的通过ErrorException转换为异常。

Tip

PHP标准库 (SPL) 提供了许多内建的异常类。

Example #1 抛出一个异常

<![CDATA[
<?php
function inverse($x) {
    if (!
$x) {
        throw new 
Exception('Division by zero.');
    }
    else 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';
?>

以上例程会输出:

0.2
Caught exception: Division by zero.
Hello World

Example #2 嵌套的异常

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

语言参考
在线手册:中文  英文

用户评论:

sander at rotorsolutions dot nl (2013-06-26 14:08:23)

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.
  
}
}

sander at rotorsolutions dot nl (2013-06-26 14:06:40)

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.
  
}
}

?>

sander at rotorsolutions dot nl (2013-06-26 14:05:33)

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.
  
}
}

?>

Edu (2013-06-24 18:13:34)

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

ian at ithomas dot name (2012-11-27 15:25:26)

For the benefit of someone anyone who hasn't come across finally blocks before, the key difference between them and normal code following a try/catch block is that they will be executed even the try/catch block would return control to the calling function.
It might do this if:
* code if your try block contains an exception type that you don't catch
* you throw another exception in your catch block
* your try or catch block calls return
They are typically used to clean up after the try block, for example by removing temporary files.

Sawsan (2011-11-28 21:36:00)

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/>";
   }

 
?>

alex dowgailenko [at] g mail . com (2011-07-27 12:20:23)

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.

Johan (2011-05-05 01:18:51)

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

fabian dot pijcke at gmail dot com (2010-09-08 02:31:28)

Be careful when you set variables inside a try block, they are still declared outside the block.

Note that it is the case for every block constructions in PHP, but it can be useful to be aware of it :)

<?php

try {
        
$a "blabla";
} catch (
Exception $e) {}

echo isset(
$a) ? ":)" ":("// Gives ":)"
echo isset($b) ? ":)" ":("// Gives ":("

?>

mcnaney at gmail (2010-08-31 10:21:27)

Watch your namespace if you have created your own exception class.

For example:

Error.php
<?php
namespace Global;
class 
Error extends Exception {}
?>

Foo.php
<?php
namespace Foo;

try {
  
$this->foo();
} catch (
Error $e) {
   echo 
$e->getMessage();
}
?>

You WILL NOT get an error message if Error is not in the Foo namespace. Instead the Exception will be passed upwards.

Should be:
<?php
try {
  
$this->foo();
} catch (Global\
Error $e) {
   echo 
$e->getMessage();
}
?>

zmunoz at gmail dot com (2010-05-18 13:05:58)

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

 }
?>

mike at clove dot com (2010-04-10 08:38:11)

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>

ask at nilpo dot com (2009-05-27 12:19:13)

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}

rctay89 at gmail dot com (2008-12-17 01:33:50)

The internal implementation of Exception.__toString is something like this:

<?php

class MyException extends Exception {
  public function 
__toString() {
    return 
"exception '".__CLASS__ ."' with message '".$this->getMessage()."' in ".$this->getFile().":".$this->getLine()."\nStack trace:\n".$this->getTraceAsString();
  }
}

?>

Useful if you want to customize your exception toString format, but not to deviate too much from the built-in one.

Shot (Piotr Szotkowski) (2008-10-21 12:13:17)

‘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).

michael dot ochs at gmx dot net (2008-03-21 03:44:17)

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();
?>

chugadie dot geo at yahoo dot com (2008-03-05 10:40:02)

@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.

omnibus at omnibus dot edu dot pl (2007-12-04 03:11:29)

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

frank at netventures dot com dot au (2007-11-07 15:43:21)

If you are going to use multiple catches within a try-catch then do not forget the stacking order of those catches!

This is important as any classes that extend the Exception class, like MyException in example 20.3, will be caught in the Exception case. This is because your newly extended class also has a class type of Exception. This baffled me for awhile as the examples here worked but mine didn't because my first catch was trying to catch Exception.

Example:

<?php

/**
 * My1Exception extends Exception
 * My2Exception extends Exception
 */

/**
 * This will always fall in the first exception
 */
try {
    throw new 
My1Exception("My fail english? That's unpossible"69);
} catch (
Exception $e) {
    print 
"Incorrect Exception";
} catch (
My1Exception $e) {
    print 
"Correct Exception but I won't be here";
} catch (
My2Exception $e) {
    print 
"Again, incorrect";
}

/**
 * Whereas here, the catch stacking order was changed so our throw will cascade into the correct catch
 */
try {
    throw new 
My1Exception("My cat's breath smells like cat food"69);
} catch (
My2Exception $e) {
    print 
"Incorrect Exception";
} catch (
My1Exception $e) {
    print 
"Correct Exception and I WILL be printed";
} catch (
Exception $e) {
    print 
"Again, incorrect";
}

?>

So, ALWAYS keep the Exception catch block at the bottom, then any of the other extended exceptions that extend from Exception, then any of your other extended exceptions that extend from those extended exceptions, etc

hartym dot dont dot like dot spam at gmail dot com (2007-10-18 04:41:30)

@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.

peter dot goodman at gmail dot com (2007-06-14 19:52:46)

I've found that exception destructors are not called unless the exception is caught.
I've created a simple solution to this problem (calling __destruct() from __toString() ) and have written up a lengthy article detailing one good use case for this method at http://ioreader.com/2007/06/14/taking-advantage-of-exceptions-in-php5/
Also, one of the useful things about using a destructor as a clean up method is that it is called at the end of a catch statement.

jon at hackcraft dot net (2007-01-24 09:52:17)

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.

dexen at google dot me dot up (2006-09-18 06:45:30)

Summary:
 * use destructors to perform a cleanup in case of exception.

PHP calls method __destruct()  on instance of class when variable storing the instance goes out-of-scope (or gets unset). This works for function leave by Exception, aside of plain return. (same as for C++, AFAIK)

aFunction() {
$i = new LockerClass();
throw new MinorErrorEx('Warn user & perform some other activity');
// $i->__destruct() gets called before stack unwind begins, unlocking whatever get locked by new LockerClass();
 return $bar;
}

(A lengthy) example:

Let's say you need to perform a series of operaions on SQL database that should not get disrupted. You lock the tables: 
<?php
function updateStuff() {
    
DB::query('LOCK TABLES `a`, `b`, `c` WRITE');
    
/* some SQL Operations */
    
someFunction();
    
/* more SQL Operations */
    
DB::query('UNLOCK TABLES');
?>

Now, let's supouse that someFunction() may throw an exception. This would leave us with the tables locked, as the second DB::query() will not get called. This pretty much will cause the next query to fail. You can do it like:
<?php
function updateStuff() {
    
DB::query('LOCK TABLES `a`, `b` WRITE');
    
/* some SQL Operations */
    
try {
        
someFunction(); }
    catch ( 
Exception $e ) {
        
DB::query('UNLOCK TABLES');
        throw 
$e;
    }
    
/* more SQL Operations */
    
DB::query('UNLOCK TABLES')
?>

However, this is rather ugly as we get code duplication. And what if somebody later modifies updateStuff() function in a way it needs another step of cleanup, but forget to add it to catch () {}? Or when we have multiple things to be cleaned up, of which not all will be valid all the time?
 
My solution using destructor: i create an instance of class DB holding a query unlocking tables which will be executed on destruction.

<?php
function updateStuff() {
    
$SQLLocker DB::locker/*read lock list*/array('a''b'), /*write lock list*/array('b') );
    
/* some SQL Operations */
    
someFunction();
    
/* $SQLLocker gets destructed there if someFunction() throws an exception */
    
DB::query('UNLOCK TABLES');
    
/* other SQL Operations */
    /* $SQLLocker gets destructed there if someFunction() does not throw an exception */
}

class 
DB {
    function 
locker $read$write ) {
        
DB::query/*locks*/);
        
$ret = new DB;
        
$ret->onDestruct 'UNLOCK TABLES';
        return 
$ret;
    }

    function 
_destructor() {
        if ( 
$this->onDestruct )
            
DB::query($this->onDestruct);
    }
}
?>

jazfresh at hotmail.com (2006-08-07 22:18:44)

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.

fjoggen at gmail dot com (2006-04-26 13:58:22)

This code will turn php errors into exceptions:

<?php
function exceptions_error_handler($severity$message$filename$lineno) { 
    throw new 
ErrorException($message0$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.

jd at wuputah dot com (2005-05-06 19:15:01)

PHP5 supports exception throwing inside a function, and catching it outside that function call. There is no mention of this in documentation but it works just fine, as tested by this sample code:

<?php

function exceptionFunction() { 
        throw new 
Exception("Throwing an exception!"); 
}

try {
        
exceptionFunction();
} catch (
Exception $e) {
        echo 
"Exception caught!\n";
}

?>

The result in PHP 5.0.3 is "Exception caught!"

Further tests show that nested functions with exceptions, methods throwing exceptions, etc all work the same way. This is like declaring all classes (or methods) in Java as "class ClassName throws Exception". While I consider this a good thing, you should be aware that any thrown exception will propagate up your stack until it is either caught or runs out of stack.

易百教程