Skip to content

7. Exceptions and Errors

When a class method encounters an unrecoverable error (file does not exist, database not connected, network connection down), it does not display an error on a console (file, database) but throws an exception. All exceptions extend the [\Exception] class. In addition to exceptions, the internal operation of PHP also generates errors whose base class is [\Error]. Both classes implement the PHP interface.

7.1. The script tree

Image

7.2. The [\Throwable] interface

The [\Throwable] interface is as follows:

Image

The role of the interface methods is as follows:

Image

7.3. The predefined exceptions in PHP 7

PHP 7 defines several exception classes:

Image

  • in [1], the exceptions predefined in PHP;
  • in [2], the exceptions from the SPL library (Standard PHP Library) of PHP 7. The SPL library is a collection of classes and interfaces designed to solve problems frequently encountered by developers.

7.4. The predefined errors in PHP 7

PHP 7 defines several error classes:

Image

The [\Error] class is the parent class of all predefined errors in PHP. The [ErrorException] class allows you to encapsulate an instance of the [\Error] class within an instance of the [\Exception] class. This allows for standardized error handling by processing only exceptions.

7.5. Example 1

The first example, [exceptions-01.php], demonstrates both PHP errors and an exception:


<?php
 
// display all errors
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// code --------
$var=[];
// unknown key
print $var["abcd"];
// division by zero
$var=7/0;
var_dump($var);
// fixed terminal board
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// index outside the limits
$array[5]=8;

Comments

  • line 4: PHP is instructed to report all errors. The second parameter is the requested error level:

Image

Image

  • line 5: we ask to display errors on the console;
  • line 9: we access a non-existent element of the [$var] array;
  • line 11: a division by zero is performed;
  • line 14: an instance of the [SplFixedArray] class is created. This class allows you to create a fixed-bound array with integer indices;
  • line 18: accesses a non-existent element of the array;

Results

1
2
3
4
5
6
7
8
9
Notice: Undefined index: abcd in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 9

Warning: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 11
float(INF)

Fatal error: Uncaught RuntimeException: Index invalid or out of range in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php:18
Stack trace:
#0 {hand}
thrown in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 18

Comments

  • line 1 of the results: accessing a non-existent key in an array causes a PHP error of level [E_NOTICE]. This does not interrupt the execution of the script;
  • Line 3 of the results: Dividing a number by zero causes a PHP error of level [E_WARNING]. This does not interrupt the execution of the script;
  • lines 6–9 of the results: accessing a non-existent index of an array [SplFixedArray] causes an exception of type [RuntimeException] and interrupts script execution;

7.6. Handling Exceptions

The script [exceptions-02.php] demonstrates how to handle exceptions:


<?php
 
// all errors are displayed
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// on entoure le code par un try / catch
try {
  $var = [];
  // unknown key
  print $var["abcd"];
  // division by zero
  $var = 7 / 0;
  var_dump($var);
  // fixed terminal board
  $array = new \SplFixedArray(5);
  $array[1] = 2;
  $array[4] = "foo";
  // index outside the limits
  $array[5] = 8;
  // check
  print "ce message ne sera pas affiché\n";
} catch (\Throwable $ex) {
  // \Throwable is the interface implemented by most errors and exceptions
  // exception is displayed
  print "erreur, message : " . $ex->getMessage() . ", type : " . get_class($ex) . "\n";
}

Comments

  • The script is the one presented in the previous paragraph. However, we have now wrapped the code in lines 8–19—which could cause errors—in a try/catch block: if the code in lines 8–21 causes (throws) an exception or error, it will be handled by the catch block in lines 22–26;
  • Line 22: The parameter of the [catch] clause is the type of exception or error we want to handle. By setting the type to [\Throwable], which is an interface, we indicate that we want to handle any class instance that implements the [\Throwable] interface. Since all error and exception classes implement this interface, the [catch] clause handles any error or exception encapsulated in a class;
  • line 19: the statement that triggers the error and raises the exception. As soon as an exception occurs, control is transferred to the [catch] clause. The code following line 19 will therefore not be executed;

Results

1
2
3
4
5
Notice: Undefined index: abcd in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-02.php on line 10

Warning: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-02.php on line 12
float(INF)
erreur, message : Index invalid or out of range, type : RuntimeException

Comments on the results

  • Lines 1 and 3: The errors [E_NOTICE] and [E_WARNING] appear here. These errors are not exceptions and are therefore not handled by the [catch] clause;
  • Line 5: The error message is written in the [catch] clause. Therefore, an exception derived from [\Exception] or an error derived from [\Error] has occurred. Here we see that this is the class [\RuntimeException];

7.7. Parameters of clause [catch]

Let’s examine the following [exceptions-03.php] script:


<?php
 
// all errors are displayed
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
 
// a fixed terminal board
$array = new \SplFixedArray(5);
try {
  // index outside the limits
  $array[5] = 8;
} catch (\Throwable $ex) {
  // error message display
  print "Erreur 1 : " . $ex->getMessage() . "\n";
}
 
try {
  // index outside the limits
  $array[5] = 8;
} catch (\Exception $ex) {
  // error message display
  print "Erreur 2 : " . $ex->getMessage() . "\n";
}
 
try {
  // index outside the limits
  $array[5] = 8;
} catch (\RuntimeException $ex) {
  // error message display
  print "Erreur 3 : " . $ex->getMessage() . "\n";
}
try {
  // division by 0
  intdiv(5, 0);
} catch (\Throwable $ex) {
  // error message display
  print "Erreur 4 : " . $ex->getMessage() . "\n";
}

try {
  // division by 0
  intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
  // error message display
  print "Erreur 5 : " . $ex->getMessage() . "\n";
}
 
try {
  // division by 0
  intdiv(5, 0);
} catch (\Error $ex) {
  // error message display
  print "Erreur 6 : " . $ex->getMessage() . "\n";
}
 
try {
  // division by 0
  intdiv(5, 0);
} catch (\Exception $ex) {
  // error message display
  print "Erreur 6 : " . $ex->getMessage() . "\n";
}

Comments

  • lines 8–31: 3 different ways to handle the exception generated by using an incorrect index with the [\SplFixedArray] class. We saw that this error generated a [RuntimeException] exception;
    • line 12: handles an error of type [\Throwable]. This is valid since the type [RuntimeException] derives from the type [\Exception], which implements the interface [\Throwable];
    • line 20: handles an error of type [\Exception]. This is valid since the type [RuntimeException] derives from the type [\Exception];
    • line 28: handles an error of type [\RuntimeException]. This is the preferred method since it is the exact type of the exception generated;
  • lines 32–62: 4 different ways to handle the exception generated by the function [intdiv] when a divisor equal to 0 is passed to it. The function [ intdiv ( int $dividend , int $divisor ) : int] performs the integer division $dividend / $divisor. When the divisor is zero, the exception [\DivisionByzeroError] is thrown;
    • line 35: we catch any error that implements the [\Throwable] interface. This is valid;
    • line 43: the exact type of the error is intercepted: this is the preferred method;
    • line 51: the type [\Error] is caught. This is valid since the class [DivisionByzeroError] extends the class [Error];
    • line 59: the type [\Exception] is intercepted. This is invalid because the class [DivisionByzeroError] has no relationship with the class [\Exception];

Results

Erreur 1 : Index invalid or out of range
Erreur 2 : Index invalid or out of range
Erreur 3 : Index invalid or out of range
Erreur 4 : Division by zero
Erreur 5 : Division by zero
Erreur 6 : Division by zero

Fatal error: Uncaught DivisionByZeroError: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php:58
Stack trace:
#0 C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php(58): intdiv(5, 0)
#1 {hand}
thrown in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php on line 58

7.8. Clause [finally]

The try/catch structure can have a third element and become a try/catch/finally structure. The code in clause [finally] is executed in the following two cases:

  • the [try] clause does not throw an exception. It is then executed in its entirety, and execution of the code proceeds to the [finally] clause, which is executed in its entirety;
  • The [try] clause throws an exception. It is then executed up to the statement that throws the exception. Code execution then proceeds to clause [catch], which is executed in full. Then code execution proceeds to clause [finally], which is executed in full;

Finally, the code in clause [finally] is still executed. This scenario is useful in the following case:

  • in [try], the code has acquired resources (files, databases, network connections, queues). These resources are generally memory-intensive. They must therefore be released (most often referred to as “closed”) as soon as possible;
  • if the resources were acquired in [try], their release will be handled in [finally]. This ensures that in all cases (whether an error occurs or not), the acquired resources are returned to the system;

The following script, [exemples/exceptions/exceptions-04.php], demonstrates how the [finally] clause works in various situations:


<?php
 
// or create an exception instance
$e = new \Exception("Erreur…");    
var_dump($e);
 
// first test
try {
  print "Premier test\n";
  throw $e;
} catch (\Exception $ex1) {
  print $ex1->getMessage() . "\n";
} finally {
  print "Terminé\n";
}

// second test
try {
  print "Second test\n";
} catch (\Exception $ex1) {
  print $ex1->getMessage() . "\n";
} finally {
  print "Terminé\n";
}
 
// third test
try {
  print "Troisième test\n";
  return;
} catch (\Exception $ex1) {
  print $ex1->getMessage() . "\n";
} finally {
  print "Terminé\n";
}

Code comments

  • line 4: $e is an instance of the predefined class [\Exception]. We will launch it in various places;
  • lines 8–15: the $e exception is thrown in [try] (line 10);
  • line 11: the [\Exception] exception is caught and its error message is written to the console;
  • lines 13–15: the [finally] clause writes a message. Based on what was mentioned earlier, this message should always be written, regardless of whether there is an error in [try];
  • lines 18–24: there is no error in [try]. Here, too, we should proceed to [finally];
  • lines 27–34: there is a [return] statement in the try block and no error. One might then wonder if we will proceed to the [finally] clause. Execution shows that we do;

Results

1
2
3
4
5
6
7
Premier test
Erreur…
Terminé
Second test
Terminé
Troisième test
Terminé

Let's examine another case, [exceptions-05.php]:


<?php
 
// fourth test
try {
  print "Quatrième test\n";
  exit;
} finally {
  print "Terminé\n";
}

Comments

  • Line 6: The [exit] statement immediately terminates script execution: the [finally] clause is not executed;
  • Lines 4–9: An example of a try/catch/finally block without the [catch] clause. This is possible;

Results

Quatrième test

7.9. Creating Your Own Exception Classes

In a somewhat large project, it is useful to differentiate between various errors by encapsulating them in different exception classes. In the previous script, we saw that any exception could be caught by a [catch (\Throwable] clause. This is recommended if you have no idea what the intercepted error is and the handling is the same for all errors. This is sometimes the case, but you often need to adapt the handling to the exact type of error. You must then distinguish between the errors.

Let’s examine the following [exceptions-06.php] script:


<?php
 
// we define our own family of exceptions
class Exception1 extends \RuntimeException {
  
}
 
class Exception2 extends \RuntimeException {
  
}
 
// or use our exceptions
$e1 = new Exception1("Erreur1…");
var_dump($e1);
$e2 = new Exception2("Erreur2…");
var_dump($e2);
 
// first test
print ("premier test\n");
try {
  // throw an Exception1 type
  throw $e1;
} catch (Exception1 $ex1) {
  print "Exception 1" . "\n";
  print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
  print "Exception 2" . "\n";
  print $ex2->getMessage() . "\n";
}
 
// second test
print ("second test\n");
try {
  // throw an Exception2 type
  throw $e2;
} catch (Exception1 $ex1) {
  print "Exception 1" . "\n";
  print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
  print "Exception 2" . "\n";
  print $ex2->getMessage() . "\n";
}
 
// third test
print ("troisième test\n");
try {
  // throw an Exception1 type
  throw $e1;
} catch (Exception1 | Exception2 $ex) {
  print "Exception 1 ou 2" . "\n";
  print $ex->getMessage() . "\n";
}
 
// fourth test
print ("quatrième test\n");
try {
  // throw an Exception2 type
  throw $e2;
} catch (Exception1 | Exception2 $ex) {
  print "Exception 1 ou 2" . "\n";
  print $ex->getMessage() . "\n";
}

Comments

  • lines 4–10: we define two classes, [Exception1] and [Exception2], both derived from the predefined class [\RuntimeException]. The body of these classes is empty. In other words, they are used solely for their types: it is because they have different types that we will be able to distinguish between these two exceptions in the [catch] clauses;
  • lines 13–16: we define two variables, $e1 and $e2, which have the types [Exception1] and [Exception2], respectively;
  • lines 20–29: there is a try/catch/catch structure. This allows for handling different exception types with different clauses [catch];
  • line 23: intercepts exceptions of type [Exception1];
  • line 26: catches exceptions of type [Exception2];
  • line 49: catches exceptions of type [Exception1] or (|) [Exception2];

Results

object(Exception1)#1 (7) {
  ["message":protected]=>
  string(10) "Erreur1…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-06.php"
  ["line":protected]=>
  int(13)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  NULL
}
object(Exception2)#2 (7) {
  ["message":protected]=>
  string(10) "Erreur2…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-06.php"
  ["line":protected]=>
  int(15)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  NULL
}
premier test
Exception 1
Erreur1
second test
Exception 2
Erreur2
troisième test
Exception 1 ou 2
Erreur1
quatrième test
Exception 1 ou 2
Erreur2

Comments on the results

  • lines 1–17: the “content” of an exception:
    • lines 2–3: the error message;
    • lines 6–7: the error code;
    • lines 8–9: the name of the file in which the exception occurred;
    • lines 10–11: the line where the exception occurred;
    • lines 15-16: the previous exception. An exception can encapsulate another exception, thereby defining an exception stack. The [previous] attribute allows you to utilize this stack;

7.10. Re-throwing an exception

An exception can be thrown multiple times, as shown in the following script [exceptions-07.php]:


<?php
 
try {
  try {
    // throw an exception
    throw new \Exception("test");
  } catch (\Exception $ex) {
    // the intercepted exception is re-launched
    throw $ex;
  } finally {
    // we'll make it to the finally
    print "finally 1\n";
  }
} catch (\Exception $ex2) {
  // the initial exception is recovered
  print $ex2->getMessage() . " dans try / catch / finally externe\n";
} finally {
  // we'll make it to the finally
  print "finally 2\n";
}

Comments

  • line 6: we throw an exception;
  • line 7: we catch it;
  • line 9: we rethrow it. It then passes through the try/catch/finally block at the next higher level;
  • line 14: it is caught again;
  • lines 10–12: the execution shows that even after the [throw] in line 9, we do indeed enter the [finally] clause of the try/catch/finally block;

Results

1
2
3
finally 1
test dans try / catch / finally externe
finally 2

7.11. Handling an exception stack

An exception can encapsulate another exception, which itself can encapsulate another, ultimately forming an exception stack. Here is an example [exceptions-08.php]:

<?php

// we define our own family of exceptions
class Exception1 extends \RuntimeException {

}

class Exception2 extends \RuntimeException {

}

class Exception3 extends \RuntimeException {

}

// or use our exceptions
$e1 = new Exception1("Erreur 1…", 1, new Exception2("Erreur 2…", 2, new Exception3("Erreur 3…")));
var_dump($e1);
// exploiting the current exception
print $e1->getMessage() . "\n";
$e = $e1;
while ($e->getPrevious() !== NULL) {
  // previous exception
  $e = $e->getPrevious();
  // error message
  print $e->getMessage() . "\n";
}

Comments

  • lines 4–14: define three exception classes derived from the predefined exception [RuntimeException];
  • line 17: an instance of the [Exception3] class is encapsulated within an instance of the [Exception2] class, which is itself encapsulated within an instance of the [Exception1] class. The constructor used here is the constructor of the [Exception] class:

Image

The third parameter of the constructor allows you to encapsulate an exception. This can be useful in the following scenario:

  • we define a method M that can generate an exception of type [Exception1] and only of this type for compatibility reasons, for example with an interface;
  • however, other types of exceptions may occur within method M. To propagate an error back to the code calling method M, we will then encapsulate these exceptions in the type [Exception1], which we will throw. This ensures that the information contained in the encapsulated exception—which was the original cause of the error—is not lost;
  • Lines 20–27 show how to manage the stack of exceptions within an exception;

Results


object(Exception1)#1 (7) {
  ["message":protected]=>
  string(11) "Erreur 1…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(1)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
  ["line":protected]=>
  int(17)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  object(Exception2)#2 (7) {
    ["message":protected]=>
    string(11) "Erreur 2…"
    ["string":"Exception":private]=>
    string(0) ""
    ["code":protected]=>
    int(2)
    ["file":protected]=>
    string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
    ["line":protected]=>
    int(17)
    ["trace":"Exception":private]=>
    array(0) {
    }
    ["previous":"Exception":private]=>
    object(Exception3)#3 (7) {
      ["message":protected]=>
      string(11) "Erreur 3…"
      ["string":"Exception":private]=>
      string(0) ""
      ["code":protected]=>
      int(0)
      ["file":protected]=>
      string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
      ["line":protected]=>
      int(17)
      ["trace":"Exception":private]=>
      array(0) {
      }
      ["previous":"Exception":private]=>
      NULL
    }
  }
}
Erreur 1…
Erreur 2…
Erreur 3…