Want to learn object-oriented programmming? Get my free course

Polymorphism and WordPress: Interfaces

Let’s talk about interfaces. As a WordPress developer, how can they be useful to you and your projects? It’s going to be a tough sell because WordPress core doesn’t use them and we’ll see why this is an issue a bit later. That said, you’ll still find this article useful if you’re looking to:

  • Learn more about PHP and not just WordPress
  • Build strong and extensible PHP code
  • Reduce bugs in your open source plugins
  • Use open source PHP frameworks

Like the article on abstract classes, you’ll get a detailed example to help you with the topic. It’ll explain how interfaces work and how you can use them. You’ll also get a good idea of the design decisions that warrant the use of an interface.

Reviewing interfaces

When we discussed inheritance, we described interfaces as a contract. If a class chooses to implement an interface, it agrees to follow its contract. The scope of the contract is quite limited. It can only define:

  • Public methods
  • Constants

Below is a small example of an interface and a class implementing it.

/**
 * Interface for implementing the conversion to an array.
 */
interface ArrayableInterface
{
    /**
     * Converts the object to an array.
     *
     * @return array
     */
    public function to_array();
}

class WP_User implements ArrayableInterface
{
    // ...

    /**
     * Converts WP_User to an array.
     *
     * @return array
     */
    public function to_array()
    {
        return array(
            'id'           => $this->id,
            'login'        => $this->user_login,
            'email'        => $this->user_email,
            'display_name' => $this->display_name,
            // ...
        );
    }
}

The interface ArrayableInterface defines a contract for converting an object to an array. It does so by requiring any class that implements it to have the method to_array. To follow the contract, that method needs to return an array with the data from the object.

Meanwhile, WP_User is a class that implements our ArrayableInterface contract. By implementing that contract, it can say, “Look you can transform me into an array!” That’s the basic idea behind interfaces.

Polymorphism with interfaces is about sharing a contract

So how does this tie to polymorphism? With abstract classes, we saw that polymorphism was about the relationship between your classes. If you wanted to share common code between your classes, you used an abstract class.

When using polymorphism with interfaces, the common element between your classes isn’t the code. It’s the contract. Sharing a common contract is a powerful tool.

To see that power, let’s go back to our previous example. ArrayableInterface is a contract to transform an object into an array. It is available to anyone who wants to use it now. They just need to check if a class implements that interface.

class JsonEncoder
{
    // ...

    /**
     * Encodes an object into JSON.
     *
     * @param mixed $object
     *
     * @return string
     */
    public function encode($object)
    {
        if (!$object instanceof ArrayableInterface) {
            return '';
        }

        return json_encode($object->to_array());
    }
}

The previous example shows how this would work in a hypothetical JsonEncoder class. The encode method checks if the object passed to it implements our ArrayableInterface. It uses theinstanceof operator to do it.

If the operator returns false, it returns an empty string. But if it returns true, you know that you can use the to_array method with confidence. You don’t even need to know the class of the object. You know that it satisfies your contract and that’s all that you should care about.

The challenge with using interfaces

It’s all nice and good that an interface creates a contract. Why would you want to create a contract in the first place? This highlight a significant difference between interfaces and abstract classes.

Abstract classes had only one type of user. That user was the developer who wanted to share code between classes. Meanwhile, interfaces have two types of users:

  • The interface creator
  • The interface implementor

The creator

The creator is the one that creates the contract and dictates how you should use it. It’s the job that people associate with interfaces. It’s also the one that is more difficult and feels less useful.

“Why should I create contracts in my own code?”

Well, most of the time, you don’t need to. The interface creator is usually the language (PHP has a lot of useful interfaces) or the platform/framework. This is where WordPress core falls short. There’s a lot of opportunities for creating interfaces for you to use in core. Right now, it has none so learning about them is a lot harder.

The implementor

Interfaces are a lot more useful to you when you use them for their contract. That’s because the creators did most of the heavy lifting for you. They created the contract as well as the classes or functions that use it.

All that they ask of you is that you implement their interface. After that, you can use their code to do amazing things. That’s exactly what was going on in the example earlier.

WP_User implemented ArrayableInterface which required that you create a to_array method. After you created that method, you could convert your class to JSON using the encoder. A useful outcome that only required adding one method to your class.

That ability to transform what your class can do is what makes interfaces powerful.

An example: WordPress Plugin API Manager

Like I pointed out before, WordPress doesn’t have any interfaces for you to use. This doesn’t mean we can’t find an example for you to see interfaces in action. We’ll just have to create our own.

That’s right! Today you’re going to take on the mantle of both interface creator and implementor! ∗gasp∗

A common scenario: registering actions and filters

It’s hard to write a plugin without using the WordPress Plugin API. Why wouldn’t you use it? It’s so useful and everyone loves it.

The hard piece of the puzzle is how to deal with all your actions and filters when using objects. These are usually handled by:

  • Putting all the actions and filters in the constructor
  • Creating a new object. Using that object and registering all your actions and filters outside the class

Our goal is to create a class that will handle registering actions and/or filters for any class. Eliminating the need for you to handle it in each of your classes.

Creating the contract

Let’s start with the contract. What do you need know when you want to register actions and/or filters for a class? You need to know which actions and/or filters that object wants to subscribe to.

namespace MyPlugin;

/**
 * HookSubscriberInterface is used by an object that needs to subscribe to
 * WordPress action and filter hooks.
 */
interface HookSubscriberInterface
{
    /**
     * Returns an array of actions that the object needs to be subscribed to.
     *
     * @return array
     */
    public static function get_actions();

    /**
     * Returns an array of filters that the object needs to be subscribed to.
     *
     * @return array
     */
    public static function get_filters();
}

This is a bare bones view of the interface. We defined the need for get_actions and get_filters methods. We defined them as static because they don’t depend on an instantiated object to work.

You also need to describe a format for what the methods will return. That’s because there are three possible cases when registering an action or filter. They are:

  • You only need to register the method name
  • You want to register the method name with a priority
  • You want to register the method name with a priority and a number of accepted arguments

The contract has to support these three cases. So let’s add some detailed documentation to the interface.

namespace MyPlugin;

/**
 * HookSubscriberInterface is used by an object that needs to subscribe to
 * WordPress action and filter hooks.
 */
interface HookSubscriberInterface
{
    /**
     * Returns an array of actions that the object needs to be subscribed to.
     *
     * The array key is the name of the action hook. The value can be:
     *
     *  * The method name
     *  * An array with the method name and priority
     *  * An array with the method name, priority and number of accepted arguments
     *
     * For instance:
     *
     *  * array('action_name' => 'method_name')
     *  * array('action_name' => array('method_name', $priority))
     *  * array('action_name' => array('method_name', $priority, $accepted_args))
     *
     * @return array
     */
    public static function get_actions();

    /**
     * Returns an array of filters that the object needs to be subscribed to.
     *
     * The array key is the name of the filter hook. The value can be:
     *
     *  * The method name
     *  * An array with the method name and priority
     *  * An array with the method name, priority and number of accepted arguments
     *
     * For instance:
     *
     *  * array('filter_name' => 'method_name')
     *  * array('filter_name' => array('method_name', $priority))
     *  * array('filter_name' => array('method_name', $priority, $accepted_args))
     *
     * @return array
     */
    public static function get_filters();
}

Our new documentation gives the implementor all the information he needs to use the interface. This is important because there is no code in an interface. Using an interface can be quite hard without detailed documentation. That’s because you don’t know what the contact expects of you.

Dividing the contract in two

The interface we have right now is good for the job. That said, let’s bring this one step further. We’re going to divide the interface into two. This allows you to make the distinction between:

  • A class that needs to add action hooks
  • A class that needs to add filter hooks

You can see the result below. The only difference is that we have two interfaces now instead of one.

namespace MyPlugin;

/**
 * ActionHookSubscriberInterface is used by an object that needs to subscribe to
 * WordPress action hooks.
 */
interface ActionHookSubscriberInterface
{
    /**
     * Returns an array of actions that the object needs to be subscribed to.
     *
     * The array key is the name of the action hook. The value can be:
     *
     *  * The method name
     *  * An array with the method name and priority
     *  * An array with the method name, priority and number of accepted arguments
     *
     * For instance:
     *
     *  * array('action_name' => 'method_name')
     *  * array('action_name' => array('method_name', $priority))
     *  * array('action_name' => array('method_name', $priority, $accepted_args))
     *
     * @return array
     */
    public static function get_actions();
}

/**
 * FilterHookSubscriberInterface is used by an object that needs to subscribe to
 * WordPress filter hooks.
 */
interface FilterHookSubscriberInterface
{
    /**
     * Returns an array of filters that the object needs to be subscribed to.
     *
     * The array key is the name of the filter hook. The value can be:
     *
     *  * The method name
     *  * An array with the method name and priority
     *  * An array with the method name, priority and number of accepted arguments
     *
     * For instance:
     *
     *  * array('filter_name' => 'method_name')
     *  * array('filter_name' => array('method_name', $priority))
     *  * array('filter_name' => array('method_name', $priority, $accepted_args))
     *
     * @return array
     */
    public static function get_filters();
}

We call what we just did the “Interface segregation principle” (It’s the “I” in SOLID). It’s the act of splitting our larger interface into two smaller interfaces. The idea behind the principle is that a large generic interface can lead to bloated classes.

That’s because the interface forces the class to implements methods that it won’t use. This makes the class bloated with no actual benefit to the implementor or the creator.

That’s why we separated the two interfaces. There’s are situations where you might need only action or filters, but not both. If we had one interface, you’d need to implement an empty method for the one you’re not using.

Creating a class with our contract

Now that we defined interfaces, we’re going to put them to use. Let’s start building our PluginAPIManager class. The main entry point is going to be the register method. Passing an object to that method will register it with the plugin API.

namespace MyPlugin;

class PluginAPIManager
{
    /**
     * Registers an object with the WordPress Plugin API.
     *
     * @param mixed $object
     */
    public function register($object)
    {
        if ($object instanceof ActionHookSubscriberInterface) {
            $this->register_actions($object);
        }
        if ($object instanceof FilterHookSubscriberInterface) {
            $this->register_filters($object);
        }
    }

    // ...
}

The register method shows the use of having two separate interfaces. It goes and checks if the object uses ActionHookSubscriberInterface. If it does, it calls the register_actions method. You can see the same thing with FilterHookSubscriberInterface and register_filters.  This is a benefit of separating our interface in two.

namespace MyPlugin;

class PluginAPIManager
{
    // ...

    /**
     * Regiters an object with all its action hooks.
     *
     * @param ActionHookSubscriberInterface $object
     */
    private function register_actions(ActionHookSubscriberInterface $object)
    {
        foreach ($object->get_actions() as $name => $parameters) {
            $this->register_action($object, $name, $parameters);
        }
    }

    /**
     * Regiters an object with all its filter hooks.
     *
     * @param FilterHookSubscriberInterface $object
     */
    private function register_filters(FilterHookSubscriberInterface $object)
    {
        foreach ($object->get_filters() as $name => $parameters) {
            $this->register_filter($object, $name, $parameters);
        }
    }

    // ...
}

The register_actions and register_filters methods are straightforward. That’s because we validated that they use the proper interface by using type hinting in the method definition.

Because we did that, we can call the respective interface method with confidence that it won’t break. Both methods loop through the hooks returned by their respective interface methods. We then pass them on to either register_action or register_filter methods.

namespace MyPlugin;

/**
 * PluginAPIManager handles registering actions and hooks with the
 * WordPress Plugin API.
 */
class PluginAPIManager
{
    // ...

    /**
     * Register an object with a specific action hook.
     *
     * @param ActionHookSubscriberInterface $object
     * @param string                        $name
     * @param mixed                         $parameters
     */
    private function register_action(ActionHookSubscriberInterface $object, $name, $parameters)
    {
        if (is_string($parameters)) {
            add_action($name, array($object, $parameters));
        } elseif (is_array($parameters) && isset($parameters[0])) {
            add_action($name, array($object, $parameters[0]), isset($parameters[1]) ? $parameters[1] : 10, isset($parameters[2]) ? $parameters[2] : 1);
        }
    }

    /**
     * Register an object with a specific filter hook.
     *
     * @param FilterHookSubscriberInterface $object
     * @param string                        $name
     * @param mixed                         $parameters
     */
    private function register_filter(FilterHookSubscriberInterface $object, $name, $parameters)
    {
        if (is_string($parameters)) {
            add_filter($name, array($object, $parameters));
        } elseif (is_array($parameters) && isset($parameters[0])) {
            add_filter($name, array($object, $parameters[0]), isset($parameters[1]) ? $parameters[1] : 10, isset($parameters[2]) ? $parameters[2] : 1);
        }
    }

    // ...
}

The register_action and register_filter methods process the hooks returned by the interface method. All the logic required to follow our detailed documentation is here.

If we have a string, we know it’s the method name. So you call add_action or add_filter with the object and the method name in an array.

Dealing with the array is more complicated. You need to check if it’s an array and if the zero index is set. That’s because the minimum required is the method name. Because the priority and number of arguments are optional, we have to check if they are there and offer a default if they aren’t.

You can see the complete class below.

namespace MyPlugin;

/**
 * PluginAPIManager handles registering actions and hooks with the
 * WordPress Plugin API.
 */
class PluginAPIManager
{
    /**
     * Registers an object with the WordPress Plugin API.
     *
     * @param mixed $object
     */
    public function register($object)
    {
        if ($object instanceof ActionHookSubscriberInterface) {
            $this->register_actions($object);
        }
        if ($object instanceof FilterHookSubscriberInterface) {
            $this->register_filters($object);
        }
    }

    /**
     * Register an object with a specific action hook.
     *
     * @param ActionHookSubscriberInterface $object
     * @param string                        $name
     * @param mixed                         $parameters
     */
    private function register_action(ActionHookSubscriberInterface $object, $name, $parameters)
    {
        if (is_string($parameters)) {
            add_action($name, array($object, $parameters));
        } elseif (is_array($parameters) && isset($parameters[0])) {
            add_action($name, array($object, $parameters[0]), isset($parameters[1]) ? $parameters[1] : 10, isset($parameters[2]) ? $parameters[2] : 1);
        }
    }

    /**
     * Regiters an object with all its action hooks.
     *
     * @param ActionHookSubscriberInterface $object
     */
    private function register_actions(ActionHookSubscriberInterface $object)
    {
        foreach ($object->get_actions() as $name => $parameters) {
            $this->register_action($object, $name, $parameters);
        }
    }

    /**
     * Register an object with a specific filter hook.
     *
     * @param FilterHookSubscriberInterface $object
     * @param string                        $name
     * @param mixed                         $parameters
     */
    private function register_filter(FilterHookSubscriberInterface $object, $name, $parameters)
    {
        if (is_string($parameters)) {
            add_filter($name, array($object, $parameters));
        } elseif (is_array($parameters) && isset($parameters[0])) {
            add_filter($name, array($object, $parameters[0]), isset($parameters[1]) ? $parameters[1] : 10, isset($parameters[2]) ? $parameters[2] : 1);
        }
    }

    /**
     * Regiters an object with all its filter hooks.
     *
     * @param FilterHookSubscriberInterface $object
     */
    private function register_filters(FilterHookSubscriberInterface $object)
    {
        foreach ($object->get_filters() as $name => $parameters) {
            $this->register_filter($object, $name, $parameters);
        }
    }
}

Implementing our interface

Let’s take a quick look at what a class implementing our interfaces would look like.

namespace MyPlugin;

class Plugin implements ActionHookSubscriberInterface, FilterHookSubscriberInterface
{
    /**
     * Get the action hooks Plugin subscribes to.
     *
     * @return array
     */
    public static function get_actions()
    {
        return array(
            'plugins_loaded' => 'on_plugins_loaded'
        );
    }

    /**
     * Get the filter hooks Plugin subscribes to.
     *
     * @return array
     */
    public static function get_filters()
    {
        return array(
            'the_title' => 'alter_title'
        );
    }

    /**
     * Alter the title.
     *
     * @param string $title
     *
     * @return string
     */
    public function alter_title($title)
    {
        // Alter the title

        return $title
    }

    /**
     * Do operations for Plugin once plugins are loaded.
     */
    public function on_plugins_loaded()
    {
        // Do something useful
    }
}

You can see that Plugin wants to use the plugins_loaded action hook with the on_plugins_loaded method. It also uses the the_title filter with the alter_title method.

It’s important to note that a class can implement as many interfaces as it wants. You only have to separate them with a comma. This is a big difference versus inheritance where you can only inherit from one class. That’s why the “Interface segregation principle” is such a powerful concept.

namespace MyPlugin;

$manager = new PluginAPIManager();
$plugin = new Plugin();

$manager->register($plugin);

Registering Plugin with the PluginAPIManager is simple as well. You only need to call register with your plugin object. It will handle the rest.

The best part of the example is that PluginAPIManager and its interfaces are reusable as is. You can use them for any number of plugin or theme projects without change.

That’s true code reusability.

This takes time

Like I mentioned before, learning these concepts takes time and practice. That’s why these examples are as detailed as possible. That way you can refer to them for help.

If you stick at it, things will start coming together and you’ll be on your way to bigger things. In the meantime, there’s plenty of topics and examples to cover!

Creative Commons License