Type Declarations using Interfaces in PHP

I’ve recently completed a preliminary code audit on an existing WordPress plugin, and one of the goals I’ve set for that audit was to decouple the code from the JavaScript library it was using, so that it could easily be extended to support future versions of that library.

A question came back about the constructor syntax I was using, and how that actually worked. I’ll use the opportunity to write the answer in form of a blog post, as I think that this is a concept that might be new to a lot of WordPress developers.

Let’s Automate Watching of TV Series!

To explain the concept, let’s assume we are writing a wrapper plugin for the awesome (and yet to be invented) TV Series Watcher V1 JavaScript library, which can automatically watch TV series for you, while you can concentrate on your actual work.

In the plugin, I would have proposed a constructor like this for the wrapper plugin:

class WatcherPlugin {
   public function __construct( WatcherInterface $watcher ) {
      [...]
   }
}

The first thing that was new to the person asking the question is the way the argument inside of the constructor is written. Instead of just having an argument, $watcher, we have two words that are related somehow (WatcherInterface $watcher) for the first argument.

As I have worked with several programming languages in the past, this is a very common thing for me. But as it turns out, this is “rather new” and less common in PHP usage.

Having a qualifier in front of an argument name is called a “type declaration“, and was only introduced with PHP 5.0 (when it was still called a “type hint”). With PHP 7.0 it was extended to be usable with scalar types too.

It tells PHP what types of arguments to accept for the function call to be valid. If you now try to call this constructor by passing it the wrong type, you’ll get a PHP error right away. You might for example try to pass in a string to the constructor:

$watcher_plugin = new WatcherPlugin( 'testing' );

This will immediately trigger a fatal error (or throw a TypeError exception in PHP7). So PHP itself simply does not allow you to call the constructor with a wrong argument. You can define what the requirements for your method are, and make sure that no one can call the method without fulfilling the requirements.

What are the Benefits of Using Type Declarations?

Here are some immediate benefits, in an arbitrary order:

  • You don’t have to check the argument before starting to process it. So, you can avoid code like this:
if ( ! $watcher instanceof WatcherInterface ) {
   return;
}
  • As a side-effect of the previous benefit, you’ll avoid hard-to-debug code where the method gets the wrong type of argument, but somehow it fits good enough for the code to not immediately break. This can happen if the argument is not immediately used, but rather passed on to other places in your code. It might even end up being stored in your database, and only causing an error a week later when it is eventually used.
    You’ll always want to have your errors be as close as possible (in source code as well as time) to your bugs!
  • It is one way of documenting what the exact requirements for your method are.

Why the Hate in PHP?

There are a few reasons why not everyone uses type declarations in PHP, and I want to quickly address these reasons.

  • It can have historical reasons.
    Before PHP5, there was no support for type declarations. PHP was built to be very “convenient” for coders, to “quickly get the job done”. With the growing success of PHP, projects became bigger and more complex. And as it turns out, “convenient in the short term” is often a synonym for “comes back to bite you later on”.
    That’s why PHP5 finally addressed this issue and added type declarations. It was not immediately possible to add them for scalar types, though, and we’ve had to wait until PHP7 for these.
  • It can indicate design flaws.
    Some developers mention that you can have differing type declarations when overriding methods, and that they prefer to have no type declarations rather than have them cause issues.
    I personally have never perceived this as an obstacle, and I suspect that this points to a flaw in the architectural design of the application, rather than a technical limitation of the language. The whole point of using type declarations and interfaces is to have consistency amongst your methods and object interactions. When you go go on and mix these up, letting and extending class change the type declaration, goes against the very principle this is based upon.
  • It is less useful in procedural programming.
    No scalar types before PHP7 together with no classes/interfaces means that type declarations are much less useful in procedural programming as they are in OOP.

Using Type Declarations with Interfaces

Okay, so now we know what a type declaration is. In our example above, we combine this with the use of an interface as well. We don’t tell PHP: “I need an object of type TVSeriesWatcherV1 to work correctly.“, but rather: “I need some kind of object that is able to fulfil the WatcherInterface contract.“. So what is this all about now?

Interfaces let you define a contract between different components, so that they can interact with each other without caring about the actual implementation of their respective partners.

UML Diagram showing the relationship between an interface, an abstract class and two concrete implementations.

The above diagram shows how such a relationship would be visualised using UML (quick PDF cheat-sheet).

The WatcherPlugin only ever interacts with the WatcherInterface, it does not know how the actual library should be implemented, or how many different versions there are. The WatcherPlugin‘s code is very simple as a result:

class WatcherPlugin {

   protected $watcher;

   public function construct( WatcherInterface $watcher ) {
      $this->watcher = $watcher;
   }

   public function play_episode( $episode ) {
      $this->watcher->watch( $episode );
   }
}

Note that the pattern above is a basic version of “Dependency Injection“, meaning that the code itself does not go about retrieving its dependencies from all over the place, getting tightly coupled to the surrounding code in the process, but rather that it gets all of its dependencies injected through its constructor. The outside code needs to make sure that WatcherPlugin gets everything it needs. WatcherPlugin then stores the reference to this dependency inside of an internal property of its own, so that it can reuse that dependency later on.

Splitting up the Implementations

What we’ve also done is that we’ve split up the actual implementation into code that is reused by each version of the library, and code that changes between versions.

The code that remains unchanged between versions goes into an abstract class AbstractWatcher that implements WatcherInterface. This lets us add all the boilerplate code that will never change between versions of the library.

Then we have two different concrete implementations in our example, TVSeriesWatcherV1 and TVSeriesWatcherV2, and each of them extends AbstractWatcher.  Using this approach, once a new version V3 comes out, we can simply add a third TVSeriesWatcherV3 that also extends AbstractWatcher and just add the small bit of code that needs to change between versions. The WatcherPlugin does not need to be changed to be able to use this new version.

So, when we combine this approach with the type declaration we’ve used above, we’ve written code that defines a very specific requirement using an interface: “WatcherPlugin needs to receive an object that it can watch()“.

Another Round of Benefits

This opens the door to another set of benefits, that go far beyond what we’ve identified before.

  • When correctly used with a robust Dependency Injection Container (for example `Auryn`), or DIC, we can have the outside code tell the DIC what exact version will be used in what context, and the DIC will handle injecting the correct version into the `WatcherPlugin`’s constructor automatically at runtime.
  • Completely external code can even extend the `AbstractWatcher` and register this external version with our DIC. The plugin “just works” with that new external version, without ever having known about it.
  • We can run properly isolated unit tests with our `WatcherPlugin` by injecting a mock object that implements `WatcherInterface`. Unit tests should always be run in isolation, so that each method can be tested individually, without relying on any external context.

Phew!

This post touches a lot of advanced concepts, and some of these only make sense if you’ve dabbled in object-oriented programming before. I hope that I’ve been able to explain this in a way that it nevertheless makes some sense to most of you.

I would love to hear from you fellow PHP developers!

  • Do you regularly use type declarations?
  • Do you make extensive use of interfaces to decouple your code?
  • What about dependency injection?

Hit the comments hard, this surely is a topic that needs more exposure in PHP!

6 Comments

  1. Enea Overclokk on March 14, 2016 at 10:20 pm

    Hi Alain,
    my answers
    – Do you regularly use type declarations?
    I started doing it but in place where I can (not in child class of WordPress)
    – Do you make extensive use of interfaces to decouple your code?
    I’d like to do it and I’ll do it from now
    – What about dependency injection?
    I just started studying itReport

  2. Enea Overclokk on March 17, 2016 at 11:19 am

    Thank you too for the link :-) there are a lot of material to study, wow :-)Report

  3. Dirk on March 21, 2016 at 8:35 am

    Thanks for sharing Alain,

    I come from C# where I used interfaces and DI all the time, but didn’t realize that this was available in PHP.

    So no, I haven’t been using it in PHP, but now that I know it’s available, I certainly will.Report

    • Alain Schlesser on March 23, 2016 at 1:14 pm

      Hey Dirk,

      Contrary to its generally bad reputation, PHP is actually a pretty robust and capable language.

      I’m glad you’ve found the article useful.

      Cheers!Report

Leave a Comment