An Exceptional Change in PHP 7.0

Project
This post is over 2 years 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!

Class constants, how do they work? (Or: You Learn Something New Every Day…)

Project
This post is over 2 years old and is probably out of date.

Yesterday on Twitter there was a conversation started by Marco Pivetta regarding a particularly horrible bit of code he had spotted:

If it’s unclear, this creates a string using sprintf() by prefixing ::PARAMNAME with the result of calling get_class() on the $api variable, and then passes that string into constant() which will give you the value of a constant using it’s string name.

At which point Elizabeth Smith chimed in to point out that $api::PARAMNAME would not work in older versions of PHP, necessitating this horrible workaround.

I then added this to the conversation:

https://twitter.com/dshafik/status/618492223966052354

Turns out, I was wrong. Whoops. Trevor Suarez created a past on 3v4l.org showing that this did indeed work, going back to PHP 5.3 even.

Additionally, with PHP 7 — thanks to Uniform Variable Syntax — there are many more ways to achieve this too, as you can see below:

Now, obviously, a lot of these are facetious, I mean, who is going to do ['Foo'][0]::BAR, really? But it’s interesting to see just how consistent, and flexible the new Uniform Variable Syntax is.

The someFunction()::CONSTANT, SomeClass::method()::CONSTANT, and $obj->method()::CONSTANT are much more realistic, and likely to be something you will use at some point.

There were a few syntaxes that didn’t work, which on the one hand, I’m grateful for, but on the other, I think at least some of them should be possible. Presented without further comment:

As with everything in any programming language however:

just because you can do something, doesn’t mean you should

Use with caution.

Changes to Engine Exceptions in PHP 7.0alpha2+

Standard
This post is over 2 years old and is probably out of date.

Pre-Release Software

This blog post is about PHP 7.0 which at the time of writing is currently pre-release software (7.0.0alpha2) and subject to change.

While updating my PHP 7 talk “What to Expect When You’re Expecting: PHP 7” for the DutchPHP Conference 2 weeks ago I noticed a small but significant change to the new Engine Exceptions feature in the newly release alpha 2.

Prior to alpha 2 and as per the Engine Exceptions RFC the exception hierarchy looked like this:

BaseException (abstract)
 ├── Exception extends BaseException
      ├── ErrorException extends Exception
      └── RuntimeException extends Exception
 └── EngineException extends BaseException
      ├── TypeException extends EngineException
      ├── ParseException extends EngineException
      └── AssertionError extends EngineException

The primary reason for doing this was to ensure two things:

  1. That \EngineException‘s didn’t get caught in pre-.7.0 catch-all blocks (i.e. catch (\Exception $e) { } to preserve backwards compatible behavior (fatal erroring appropriately
  2. That it was possible to build new catch-all blocks for all both old and new exceptions using catch \BaseException $e) { }

However, for alpha2 this hierarchy changed. Engine Exceptions lost their “Exception” suffix, and became \Error and and \*Error exceptions, and the abstract \BaseException class was changed to a \Throwable interface. This makes the new exception hierarchy look like this:

Throwable interface
 ├── Exception implements Throwable
      ├── ErrorException extends Exception
      └── RuntimeException extends Exception
 └── Error implements Throwable
      ├── TypeError extends Error
      ├── ParseError extends Error
      └── AssertionError extends Error

With these changes, you still cannot create your own exception hierarchy as you cannot implement \Throwable, you must still extend from \Exception or \Error exceptions (or their children), however you can extend \Throwable and (while still extending \Exception or \Error) implement that new interface.

Personally, I prefer this hierarchy, but, as with any pre-release software: everything is subject to change!

Edit:
As pointed out to me on Twitter, there is an RFC for this change: Throwable interface RFC

Speaking at phpDay 2015

Speaking
This post is over 3 years old and is probably out of date.

I will be returning to Verona for phpDay 2015 in just a few weeks to give two presentations, Writing Faster PHP with HHVM & Hack, and a new talk on PHP 7:

What to Expect When You’re Expecting: PHP 7

PHP 7 is coming, and with it will come new features, backwards compatibility breaks, and huge performance gains.

This talk will get you prepared for all the changes in this upcoming release and offer practical advice you can implement now to ensure you code still works come upgrade time.

I’m excited to give my first talk on PHP 7, and excited to go back to Verona — if you have the opportunity to get there, it’s a beautiful city, with wonderful people, and don’t forget to sample the amazing Gelato!

Hopefully I’ll see you there!