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, PHP’s internal operations also generate errors whose base class is the [\Error] class. Both classes implement the PHP [\Throwable] interface.
7.1. The script directory structure

7.2. The [\Throwable] interface
The [\Throwable] interface is as follows:

The role of the interface’s methods is as follows:

7.3. Predefined exceptions in PHP 7
PHP 7 defines several exception classes:

- in [1], the predefined exceptions in PHP;
- in [2], the exceptions from the SPL (Standard PHP Library) in PHP 7. The SPL is a collection of classes and interfaces designed to solve problems frequently encountered by developers.
7.4. Predefined errors in PHP 7
PHP 7 defines several error classes:

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 dealing only with 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-size array
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// Index outside the bounds
$array[5] = 8;
Comments
- Line 4: We tell PHP to report all errors. The second parameter is the requested error level:


- line 5: we ask to display errors on the console;
- line 9: we access a non-existent element of the array [$var];
- line 11: we perform a division by zero;
- line 14: we create an instance of the [SplFixedArray] class. This class allows us to create an array with fixed bounds and integer indices;
- line 18: we access a non-existent element of the array;
Results
Comments
- Line 1 of the results: Accessing a non-existent array key causes a PHP error of level [E_NOTICE]. This does not interrupt script execution;
- 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 a [RuntimeException] and interrupts script execution;
7.6. Handling Exceptions
The script [exceptions-02.php] demonstrates how to handle exceptions:
<?php
// display all errors
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// wrap the code in a try/catch block
try {
$var = [];
// unknown key
print $var["abcd"];
// division by zero
$var = 7 / 0;
var_dump($var);
// fixed-size array
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// index outside the bounds
$array[5] = 8;
// check
print "this message will not be displayed\n";
} catch (\Throwable $ex) {
// \Throwable is the interface implemented by most errors and exceptions
// display the exception
print "error, message: " . $ex->getMessage() . ", type: " . get_class($ex) . "\n";
}
Comments
- This 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 clause in lines 22–26;
- Line 22: The parameter of the [catch] clause is the type of exception or error to be handled. By specifying the type as [\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 here 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 flows to the [catch] clause. The code following line 19 will therefore not be executed;
Results
Comments on the results
- lines 1 and 3: we see errors of level [E_NOTICE] and [E_WARNING]. These errors are not exceptions and are therefore not handled by the [catch] clause;
- line 5: we have the error message written in the [catch] clause. An exception derived from [\Exception] or an error derived from [\Error] has therefore occurred. Here we see that it is the [\RuntimeException] class;
7.7. Parameters of the [catch] clause
Let’s examine the following [exceptions-03.php] script:
<?php
// display all errors
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// an array with fixed bounds
$array = new \SplFixedArray(5);
try {
// Index out of bounds
$array[5] = 8;
} catch (\Throwable $ex) {
// display error message
print "Error 1: " . $ex->getMessage() . "\n";
}
try {
// Index out of bounds
$array[5] = 8;
} catch (\Exception $ex) {
// display error message
print "Error 2: " . $ex->getMessage() . "\n";
}
try {
// Index out of bounds
$array[5] = 8;
} catch (\RuntimeException $ex) {
// display error message
print "Error 3: " . $ex->getMessage() . "\n";
}
try {
// division by 0
intdiv(5, 0);
} catch (\Throwable $ex) {
// display error message
print "Error 4: " . $ex->getMessage() . "\n";
}
try {
// division by 0
intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
// display error message
print "Error 5: " . $ex->getMessage() . "\n";
}
try {
// division by 0
intdiv(5, 0);
} catch (\Error $ex) {
// display error message
print "Error 6: " . $ex->getMessage() . "\n";
}
try {
// division by 0
intdiv(5, 0);
} catch (\Exception $ex) {
// display error message
print "Error 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 generates a [RuntimeException];
- line 12: handles an error of type [\Throwable]. This is valid since the [RuntimeException] type derives from the [\Exception] type, which implements the [\Throwable] interface;
- line 20: handles an error of type [\Exception]. This is valid since the [RuntimeException] type derives from the [\Exception] type;
- 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 [intdiv] function when passing it a divisor equal to 0. The [intdiv(int $dividend, int $divisor): int] function performs the integer division $dividend / $divisor. When the divisor is zero, the [\DivisionByzeroError] exception is thrown;
- line 35: we catch any error that implements the [\Throwable] interface. This is valid;
- line 43: we catch the exact type of the error: 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 [\Exception] type is caught. This is invalid because the [DivisionByzeroError] class has no connection to the [\Exception] class;
Results
7.8. [finally] clause
The try/catch structure can have a third element and become a try/catch/finally structure. The code in the [finally] clause is executed in the following two cases:
- the [try] clause does not throw an exception. It is then executed in full, and execution of the code proceeds to the [finally] clause, which is executed in full;
- the [try] clause throws an exception. It is then executed up to the statement that throws the exception. Execution then proceeds to the [catch] clause, which is executed in full. Execution then proceeds to the [finally] clause, which is executed in full;
Finally, the code in the [finally] block is always executed. This scenario is useful in the following case:
- in the [try] block, 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 resources were acquired in the [try] block, their release should be placed in the [finally] block. This ensures that, in all cases (whether an error occurs or not), the acquired resources are released back to the system;
The following script [examples/exceptions/exceptions-04.php] demonstrates how the [finally] clause works in various situations:
<?php
// or creates an exception instance
$e = new \Exception("Error…");
var_dump($e);
// first test
try {
print "First test\n";
throw $e;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Done\n";
}
// second test
try {
print "Second test\n";
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Done\n";
}
// third test
try {
print "Third test\n";
return;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Done\n";
}
Code comments
- line 4: $e is an instance of the predefined [\Exception] class. We will throw it in various places;
- lines 8–15: the $e exception is thrown in the [try] block (line 10);
- line 11: the [\Exception] exception is caught and its error message is written to the console;
- lines 13–15: the [finally] clause prints a message. Based on what was mentioned earlier, this message should always be printed, regardless of whether there is an error in the [try] block;
- lines 18–24: there is no error in the [try]. We should also enter the [finally] block here;
- lines 27–34: there is a [return] statement in the try block and no error. One might wonder, then, if we will enter the [finally] clause. The execution shows that we do;
Results
Let's examine another case [exceptions-05.php]:
<?php
// fourth test
try {
print "Fourth test\n";
exit;
} finally {
print "Done\n";
}
Comments
- line 6: the [exit] statement immediately stops the script from running: the [finally] clause is not executed;
- lines 4–9: an example of try / catch / finally without a [catch] clause. This is possible;
Results
7.9. Creating your own exception classes
In a somewhat large project, it is useful to differentiate between different 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 caught 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 exception family
class Exception1 extends \RuntimeException {
}
class Exception2 extends \RuntimeException {
}
// or use our exceptions
$e1 = new Exception1("Error1…");
var_dump($e1);
$e2 = new Exception2("Error2…");
var_dump($e2);
// first test
print ("first test\n");
try {
// we throw an Exception1
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
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 ("third test\n");
try {
// we throw an Exception1
throw $e1;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 or 2" . "\n";
print $ex->getMessage() . "\n";
}
// fourth test
print ("fourth test\n");
try {
// we throw an Exception2
throw $e2;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 or 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, we use them only 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, with the types [Exception1] and [Exception2], respectively;
- lines 20–29: we have a try/catch/catch structure. This allows us to handle different types of exceptions with different [catch] clauses;
- line 23: catches exceptions of type [Exception1];
- line 26: catches exceptions of type [Exception2];
- line 49: catches exceptions of type [Exception1] or (|) [Exception2];
Results
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 access this stack;
7.10. Re-throwing an exception
An exception can be thrown multiple times, as shown in the following [exceptions-07.php] script:
<?php
try {
try {
// throw an exception
throw new \Exception("test");
} catch (\Exception $ex) {
// rethrow the caught exception
throw $ex;
} finally {
// we will definitely enter the finally block
print "finally 1\n";
}
} catch (\Exception $ex2) {
// we successfully catch the initial exception
print $ex2->getMessage() . " in the outer try / catch / finally block\n";
} finally {
// we will successfully enter the finally block
print "finally 2\n";
}
Comments
- line 6: we throw an exception;
- line 7: we catch it;
- line 9: we re-throw it. It then passes through the try/catch/finally block at the top level;
- line 14: it is caught again;
- lines 10–12: execution shows that even after the [throw] in line 9, control does indeed flow into the [finally] clause of the try/catch/finally block;
Results
7.11. Working with a stack of exceptions
An exception can encapsulate another exception, which itself can encapsulate another, ultimately forming an exception stack. Here is an example [exceptions-08.php]:
Comments
- lines 4–14: define three exception classes derived from the predefined [RuntimeException] exception;
- 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:

The third parameter of the constructor allows an exception to be encapsulated. 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 within the [Exception1] type, 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 nested within an exception;
Results
object(Exception1)#1 (7) {
["message":protected]=>
string(11) "Error 1…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(1)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-examples\examples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
object(Exception2)#2 (7) {
["message":protected]=>
string(11) "Error 2…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(2)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-examples\examples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
object(Exception3)#3 (7) {
["message":protected]=>
string(11) "Error 3…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-examples\examples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
NULL
}
}
}
Error 1…
Error 2…
Error 3…