• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 位置: php 中文手册 -> php 语言

    php 异常处理

    PHP 5有一个类似于其他编程语言的异常模型。异常可以是thrown,并在PHP中捕获(“catched”)。代码可能被包围在try块中,以便于捕获潜在的异常。每个try必须至少有一个对应的catch或finally块。

    抛出的对象必须是异常类的实例或异常的子类。尝试抛出一个不是的对象将导致PHP致命错误。

    catch

    多个catch块可用于捕获不同类别的异常。在按顺序定义的最后一个catch块之后,将继续正常执行(当try块中未引发异常时)。异常可以在catch块中thrown(或重新抛出)。

    当抛出异常时,不会执行语句后面的代码,PHP将尝试查找第一个匹配的catch块。如果未捕获异常,则将发出PHP致命错误,并显示“Uncaught Exception ...”消息,除非已使用set_exception_handler()定义了处理程序。

    finally

    在PHP 5.5及更高版本中,也可以在catch块之后指定或不指定finally块。finally块中的代码将始终在trycatch块之后执行,而不管是否引发了异常,以及在正常执行摘要之前执行。

    注释

    Note:

    内部PHP函数主要使用错误报告,只有现代的面向对象扩展使用异常。但是,错误可以简单地转换为带有ErrorException的异常。


    Tip

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

    范例

    <?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 #4 Exception handling with afinallyblock

    <?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";
    }
    // Continue execution
    echo "Hello World\n";
    ?>
    

    以上例程会输出:

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

    Example #5 Nested Exception

    <?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!"
    
    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());
      }
     }
     }
    ?>
    
    If a TRY has a FINALLY, a RETURN either in the TRY or a CATCH won't terminate the script. Code in the same block after the RETURN will not be executed, and the RETURN itself will be "copied" to the bottom of the FINALLY block to be executed.
    a RETURN in the FINALLY block will override value(s) returned from the TRY or a CATCH block.
    An EXIT or a DIE always terminate the script after themselves.
    code 1
    <?php
      function foo(){
        $bar = 1;
        try{
          throw new Exception('I am Wu Xiancheng.');
        }catch(Exception $e){
          return $bar;
          $bar--; // this line will be ignored
        }finally{
          $bar++;
        }
      }
      echo foo(); // 2
    ?>
    code 2
    <?php
      function foo(){
        $bar = 1;
        try{
          throw new Exception('I am Wu Xiancheng.');
        }catch(Exception $e){
          return $bar;
          $bar--; // this line will be ignored
        }finally{
          $bar++;
          return $bar;
        }
      }
      echo foo(); //2 
    ?>
    code 3
    <?php
      function foo(){
        $bar = 1;
        try{
          throw new Exception('I am Wu Xiancheng.');
        }catch(Exception $e){
          return $bar;
          $bar--; // this line will be ignored
        }finally{
          return 100;
        }
      }
      echo foo(); //100
    ?>
    
    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}
    <?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 !"
    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());
    }
    ?>
    
    catch doesn't check for the existence of the Exception class, so avoid typo.
    <?php
      class MyException extends Exception
      {
       ...
      }
      try
      {
       throw new MyException(...);
      }
      catch (MuException $e) // <--- typo
      {
       ...
      }
    ?> 
    You WON'T get
      Fatal error: Class MuException could not be loaded ...
    You WILL get
      Fatal error: Uncaught exception 'MyException' ...
    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();
      }
    ?>
    
    Using a return statement inside a finally block will override any other return statement or thrown exception from the try block and all defined catch blocks.  Code execution in the parent stack will continue as if the exception was never thrown. 
    Frankly this is a good design decision because it means I can optionally dismiss all thrown exceptions from 1 or more catch blocks in one place, without having to nest my whole try block inside an additional (and otherwise needless) try/catch block.
    This is the same behavior as Java, whereas C# throws a compile time error when a return statement exists inside a finally block. So I figured it was worth pointing out to PHP devs who may not have any exposure to finally blocks or how other languages do it.
    <?php
    function asdf()
    {
      try {
        throw new Exception('error');
      }
      catch(Exception $e) {
        echo "An error occurred";
        throw $e;
      }
      finally {
            //This overrides the exception as if it were never thrown
        return "\nException erased";
      }
    }
    try {
      echo asdf();
    }
    catch(Exception $e) {
      echo "\nResult: " . $e->getMessage();
    }
    ?>
    The output from above will look like this:
      An error occurred
      Exception erased
    Without the return statement in the finally block it would look like this:
      An error occurred
      Result: error
    This page states, "Code may be surrounded in a try block, to facilitate the catching of potential exceptions." There are two ways in which code may be "surrounded". The obvious (intuitive) way is lexical enclosure: all the code to be protected by the try block is surrounded like this:
    try
      {
      A();
      function A()
       {
       throw new Exception('error');
       echo "A()";
       } // A
      } // try
    catch...
    The other way to enclose is by execution (dynamic enclosure), like this:
    try
      {
      A();
      } // try
    catch...
    function A()
      {
      throw new Exception('error');
      echo "A()";
      } // A
    The sense in which code is "surrounded" for exception handling is dynamic (the second example above). This is because exception handling is dynamic and works by "unwinding" the execution stack.
    Don't forget if your source file is in a namespace you will need to
    <?php
    use Throwable; // PHP 7
    use Exception;
    ?>
    or type the fully qualified class name (prefixed with `\`) as in
    <?php
    try {
     // ...
    } catch(\Throwable $e) {
     // PHP 7
    } catch(\Exception $e) {
     // PHP 5
    }
    ?>
    otherwise you will only ever catch exceptions of the possibly non-existent classes `\YourNamespace\Throwable` and `\YourNamespace\Exception`!
    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.
    Type declarations will trigger Uncaught TypeError when a different type is passed. And it cannot be caught with the Exception class.
    <?php
      function xc(array $a){    
      }  
      try{
        xc(4);
      }catch (Exception $e){ // failed to catch it
        echo $e->getMessage();
      }
    ?>
    You should use TypeError instead for PHP 7+,
    <?php
      function xc(array $a){  
      }  
      try{
        xc(4);
      }catch (TypeError $e){
        echo $e->getMessage();
      }
    ?>
    In php version prior to 7.0, you should translate Catchable fatal errors to an exception and then catch it.
    <?php
      function exceptionErrorHandler($errNumber, $errStr, $errFile, $errLine ) {
        throw new ErrorException($errStr, 0, $errNumber, $errFile, $errLine);
      }
      set_error_handler('exceptionErrorHandler');
      function s(array $a){    
      }
      try{
        s(4);
      }catch (Exception $e){
        echo $e->getMessage();
      }
    ?>
    
    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.
     }
    }
    ?>
    
    ‘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).
    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";
    }
    ?>
    
    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.
    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.
    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.
    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();
    ?>
    
    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/>";
      }
     ?>
    
    Example for PHP 7.0 or latest:
    <?php
    namespace SomeNamespace;
    use Throwable, Exception, Error;
    class SomeClass
    {
      public function someMethod()
      {
        try {
          if (rand(0, 1)) {
            throw new Exception('Some message');
          } else {
            throw new Error('Some message');
          }
        } catch (Throwable $e) {
          \var_dump($e->getMessage());
        }
      }
      
      public function __toString()
      {
        try {
          return (string) $this->someMethod();
        } finally {
          return '';
        }
      }
    }
    ?>
    
    <?php
    function Test_Extention($var1)
    {
      if($var1 == false)
      {
        throw new Exception('Invalid !');
      }
    }//end function
    try
    {
      Test_Extention(false);
      printf("%s","Valid");
    }
    catch(Exception $e)
    {
      printf("%s","Message : " . $e->getMessage());
    }
    ?>
    
    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.
    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.
     }
    }
    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.
    You should put your whole program into a try/catch block. Doing so is considered a best practice, because it will allow the user to continue to work even when there are errors in the page!
    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.
    Remember that Exceptions are also objects and can be handled as such; they can be constructed in and returned as values from functions, passed as arguments to other functions, and examined before being thrown. You don't have to throw it as soon as you have constructed it (the stack trace will of course reflect the moment the Exception was constructed, not the moment it was thrown).
    You might, for example, want to collect additional information to include in YourException but you don't want to clutter up the YourException class or the code containing the "throw" statement by collecting the information there. Or you might want to do something (such as logging) with each Exception that is thrown from a certain region (catch it, pass it to the logging function, then rethrow it).
    Please don't do something like this:
    <?php
    try {
      // experimental stuff to check whether something works
    } catch(\Throwable $e) {
      return false;
    } finally {
      return true;
    }
    ?>
    Well, it is allowed syntax, it is bad practice, because once it hits the exception block, finally won't be called(because it exits the scope). Finally was invented to prevent duplicated code after one operation, regardless whether it was successful(for example closing the connection to a server). This way, you don't have any duplicated code. So you don't need to use this structure.
    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 );
    }
    ?>