A new type of PHP, part 1: Return types

Every significant release of PHP adds a number of new features, with one or two that really define the release.  For PHP 5.3, it was namespaces and anonymous functions.  For PHP 5.4, it was traits.  For PHP 5.5, generators.  For PHP 5.6, variadics.

While PHP 7 has a large array of new improvements to make developers lives easier and involve less hair loss, I believe that long term the improvements to how PHP handles variable typing will be the defining feature of this release.  It’s not a single change but actually a combination of a few closely-related changes that will change the way we write PHP, and for the better.

Why does strong typing support matter?  Because types serve as a “junior version” of your code.  They provide a program – either the compiler or the runtime – and other developers with valuable information about what the developer is trying to do without having to run the program itself.  That enables three benefits:

  1. It becomes much easier to communicate the intent of a piece of code to other developers, who can understand the code more readily. It’s like documentation but better!
  2. Because explicit typing forces more narrowly-defined behavior, it encourages more decoupled and single-purpose code; that’s valuable in its own right.
  3. Because a program can read and understand explicit types just as well as a human can, a program can analyze your code and find bugs for you… before you even try running it!

In the vast majority of cases, explicitly typed code will be easier to understand, better structured, and have fewer bugs than untyped or loosely typed code.  And for the small percentage of cases where strong typing is really more trouble than it’s worth, don’t worry; PHP’s type checking is still all opt-in, as it always has been.  But really, you should opt-in almost all the time.

Return types

The first addition to the type system is support for return types.  Functions and methods can now specify the type of value they return explicitly.  Consider the following example:

In this rather mundane example, we have an Employee object that has only one meaningful property, a well-formed US postal address.  Note the syntax of the getAddress() method, however.  After the function parameters we have a colon and a type.  That type is the one and only legal type that the return value of that method may have.

The postfix syntax for return types may look odd to developers used to C, C++, or Java, where it comes before the method.  However, as a practical matter that didn’t work for PHP as it would have confused the parser with the many optional keywords that can come before the function name.  Instead, PHP opted for a postfix notation similar to Go, Rust, or Scala.

The result is that we know, as does PHP itself, that what comes back from getAddress() can only ever be an Address object.  Any other option is an error, and will throw a TypeError.  Even null won’t satisfy that requirement; it must be an Address. That’s what makes our print statement safe: We know, with absolute certainty, that the return value is a full and real Address object, not null, not false, not a string, not some other object.  And therefore we can make assumptions about what we can safely do with it, which makes our own code cleaner.  If somehow it’s not, PHP will catch it for us because the author of getAddress() messed up by promising an Address and not coming through.

But what if we have a less trivial case, and there is no Address?  Let’s introduce an EmployeeRepository, which of course may not have a given record.  First we give Employee an ID:

And now create our repository.  (We’re just stubbing out dummy data in the constructor for now, but in practice of course you’d have a database of some sort behind it.)

Most readers will quickly notice that findById() is quite buggy, as if we ask for a non-existent Employee ID PHP will return NULL, and then our getAddress() call will die with the dreaded “method called on non-object” error.  But that’s not actually where the bug is.  The bug is that findById() is supposed to return an Employee; if it doesn’t findById() has the bug, not our code.  By specifying a return type of Employee, we make it clear whose bug it is.

What do we do if there really is no such employee?  There’s two options: One is to throw an exception; if we can’t return what we promised we’d return, that’s cause for special handling outside of the normal flow of our code.  The other is to specify an interface to return instead (which you really really should do) and define an “empty” implementation of it.  That way, remaining code will still work (as in, not fatal) and we can control what happens in “empty” cases.

Which approach you take depends on the use case, and what the implications of an object of the correct type not being available are.  In the case of a repository like here, I would argue an exception is preferred as the odds of the code that follows being able to work are minimal.  If we were dealing with a value object with a logical “empty” – say, Address – then an “empty value” object is probably workable.  Let’s modify our code accordingly (Just the parts with changes are shown for brevity):

Now getStreet() will give us a nice empty value, while a missing employee is cause for a freak-out, er, Exception.

One other important note about return types: When extending a class, the return type cannot be changed, even to make it more specific (to a subclass for instance).  The reason for that is PHP’s lazy-loaded nature.  The engine doesn’t know what classes are defined when compiling the code, so it doesn’t know if Address really is a specialization of AddressInterface.

Return types are great, but they’re not the only addition to PHP’s type system. In part 2, we’ll look at the other, arguably more important change: Scalar typing.

PHP 7 Resources

Jumpstart Training

Get the knowledge you need to ensure your applications are ready to take advantage of the innovations and performance enhancement introduced in PHP7.

Reserve your spot now >