The exceptions in PHP are not a new concept. They have been around ever since PHP 5 was released. However, they did not encompass all of PHP's error handling because errors were not considered to be exceptions. PHP, at the time, had two-error handling systems. This made it tricky to deal with, as traditional errors were not catchable via the try...catch
blocks exceptions. Certain tricks were possible, where one could have used the set_error_handler()
function in order to set a user-defined error handler function, basically listening for errors and turning them into exceptions.
Let's look at the following example:
<?php class Mailer { private $transport; public function __construct(Transport $transport) { $this->transport = $transport; } } $transport = new stdClass(); try { $mailer = new Mailer($transport); } catch (\Exception $e) { echo 'Caught!'; } finally { echo 'Cleanup!'; }
PHP 5 would not be able to catch this, and instead throws Catchable fatal error
, as shown here:
Catchable fatal error: Argument 1 passed to Mailer::__construct() must be an instance of Transport, instance of stdClass given, called in /index.php on line 18 and defined in /index.php on line 6.
By adding the implementation of set_error_handler()
before this code, as follows, we could turn that fatal error into an exception:
set_error_handler(function ($errno, $errstr) { throw new \Exception($errstr, $errno); });
With the preceding code in place, the try...catch...finally
blocks would now kick in as intended. However, there were error types that could not be caught with set_error_handler
, such as E_ERROR
, E_PARSE
, E_CORE_ERROR
, E_CORE_WARNING
, E_COMPILE_ERROR
, E_COMPILE_WARNING
, and most of E_STRICT
raised in the file where set_error_handler
is called.
The PHP 7 release improved the overall error handling system by introducing the Throwable
interface, and moving the errors and exceptions under its umbrella. It is now the base interface for any object that can be thrown via a throw
statement. While we cannot extend it directly, we can extend the \Exception
and \Error
classes. While \Exception
is the base class for all PHP and user exceptions, \Error
is the base class for all internal PHP errors.
We could now easily rewrite our preceding try...catch...finally
block into one of the following:
<?php // Case 1 try { $mailer = new Mailer($transport); } catch (\Throwable $e) { echo 'Caught!'; } finally { echo 'Cleanup!'; } // Case 2 try { $mailer = new Mailer($transport); } catch (\Error $e) { echo 'Caught!'; } finally { echo 'Cleanup!'; }
Notice the use of \Throwable
in the first example catch
block. Even though we cannot extend it, we can use it as a shorthand for catching both \Error
and \Exception
in a single catch
statement.
Implementation of \Throwable
brings a much needed alignment between errors and exceptions, making them easier to reason with.