An Exceptional Change in PHP 7.0

Project
This post is over 1 year old and is probably out of date.

With PHP 7 errors and exceptions are undergoing major changes. For the first time, the PHP engine will start to emit exceptions instead of standard PHP errors for (previously) fatal, and catchable fatal errors. This means that we can now handle them much more gracefully with try... catch.

But with this change, comes a whole new exception hierarchy:

At the top we now have an interface, \Throwable, which the original \Exception implements. Earlier versions did not have the interface and the root of the hierarchy was \Exception. We then have the new \Error exception, which is a sibling of \Exception as opposed to extending it, which also implements the new interface.

The reason \Error does not extend \Exception is so that the new exceptions will not get accidentally caught by legacy catch-all statements (catch (\Exception $e) { }) — and just like in older PHP versions, an uncaught exception is still a regular fatal error, preserving backwards compatibility.

If the ability to create a real catch-all is desired, you can catch the \Throwable interface. This means that to catch both regular exceptions, and engine exceptions, you would use catch (\Throwable $e) { } instead.

Error Exceptions

As you can see above, there are four new error exceptions, each one used for a different purpose:

\Error

Standard PHP fatal, and catchable-fatal are now thrown as \Error exceptions. These will continue to cause a “traditional” fatal error if they are uncaught.

\AssertionError

With PHP 7, we also have enhancements to assertions, using the assert() function, with the addition of zero-cost assertions, and the ability to have them throw exceptions. To enable this, you should simply set assert.exception to 1 in your php.ini (or via ini_set()).

These exceptions are (you guessed it) \AssertionError exceptions.

\ParseError

Thanks to error exceptions, you can now handle includes with parse errors, and eval() parse errors, as both now throw \ParseError exceptions:

\TypeError

With the introduction of scalar, and (especially) strict types in PHP 7, these will also throw exceptions when a type mis-match occurs. It is important to understand that this does not apply only to scalar type hints, but to traditional type hints such as class/interface names, callable and array.

Catchable Fatal Errors

Another important change in PHP 7 is with catchable fatal errors. Previously, these would have been caught and handled using set_error_handler(). However, with PHP 7, they are now \Error exceptions, which, because an uncaught exception is now a real fatal error, will no-longer be catchable in set_error_handler().

This is a backwards compatibility break and means that to work in both PHP 5.x and 7, you need to use both set_error_handler() and try... catch.

This is considered a minor BC break due to limited usage.

\Throwable and Userland

It would not be a big jump to conclude that now we have a common interface, we could create our own branches in the exception hierarchy for completely custom exceptions by simply implementing the \Throwable interface. Unfortunately, due to the fact that exceptions are magical under the hood, to be able to do things like capture line/file and stack trace information — this means that you still must still extend either \Exception or \Error, and cannot directly implement \Throwable alone.

Trying to implement \Throwable results in the following:

However, this is not the full story. You can extend \Throwable and then — while still extending \Error or \Exception — you can implement your extended interface:

Fin

As alluded to in the (pun intended) title of this post, these changes are actually quite big, allowing us to gracefully handle almost all previously fatal errors. The fact that the core team were able to maintain almost complete backwards compatibility while doing so is astounding. Kudos to them!