PHP Interfaces - What are they for and why should I use them?

Interfaces allow you to define what you expect of an object. You can think of them as if they were a contract whose terms require certain methods to exist. When a class implements a given interface they are agreeing to the terms of the contract.

This ability to define a contract for code is most useful when you are designing a system which can be extended or have certain parts swapped out. Each part of the system defines an interface laying out what actions are required to do its job. Implementations then define how exactly those actions work by providing definitions for the functions in the interface.

When someone wants to define their own custom implementation for your system, they will know what they need to do by looking at what the interface requires of them. Your code can also be assured that their custom implementation meets the requirements by checking if it implements your defined interface.

Interfaces vs Base classes

An alternative to interfaces that some people use is defining an abstract base class. This class contains all the methods required just like an interface, but defines them as abstract (or empty) and the implementation is expected to override or implement the methods.

One advantage to this is you can provide some default implementations in this class, possibly allowing for easier implementations.

The major downside to this approach though is that it locks a class into a single implementation. PHP only allows a class to extend one base class. With interfaces however, a class may implement as many as it needs. This ability to implement multiple interfaces can make some problems much easier.

Say for example, you want to build a plugin for some application. Creating a plugin requires implementing a plugin API that allows the application to initialize your plugin and pass in some necessary objects.

In order to do anything useful in your plugin though you need to tie into the events system the application provides. Tying into these events requires registering one or more objects that implement the events API.

Using the abstract base class method, you'd have to define two separate classes. One that extends the plugin API and a separate one for the events API. You might also have to implement some method of sharing information between these classes. As an example, you might implement the plugin like so:

<?php

class MyPlugin extends PluginAPI {
   public function Initialize($app){
        $evthandler = new MyPluginEvents($this);
        $app->AddEventHandler($evthandler);
   }
}

class MyPluginEvents extends EventsAPI {
   protected $plugin;
   public function __construct($plugin){
       $this->plugin = $plugin;
   }

   public function OnEvent($type, $params){
       //do something to handle the event.
   }
}

With an interface based design however, we could combine everything into a single class.

class MyPlugin implements IPluginAPI, IEventsAPI {
   public function Initialize($app){
        $app->AddEventHandler($this);
   }

   public function OnEvent($type, $params){
       //do something to handle the event.
   }
}

That code is much simpler and easier to follow. The Initialize method is required by the IPluginAPI interface and allows the plugin to register its event handlers and perform any other startup functions. The OnEvent method is defined by the IEventsAPI and allows the plugin to receive and respond to any events the application generates.

Since both of these methods reside in the same class, any data required by both methods (such as the $app object) can easily be shared using a class property.

The Iterator example

A common example given when trying to explain PHP Interfaces is PHP's built-in Iterator interface. This interface is defined by PHP and allows you to control how PHP will behave when your object is used in a foreach loop.

In order for PHP to loop over your object, it needs to be able to do a few specific operations:

  • Move the beginning of the list
  • Determine the current item key and value
  • Move to the next item in the list
  • Determine if any more items exist in the list.

You can tell PHP how to do these actions by implementing the Iterator interface. The interface defines the following methods that correspond to the above operations:

When PHP encounters an object being used in a foreach statement, it will first test if the object implements the Iterator interface. If it does it will proceed to loop over the object by way of the above methods. In terms of PHP code, that would look like this:

if ($obj instanceof Iterator){
    $obj->rewind();
    while ($obj->valid()){
       $currentValue = $obj->current();
       $currentKey = $obj->key();

       doLoopBody($currentKey, $currentValue);

       $obj->next();
    }
}

Combining Interfaces and Base classes

If you're defining an interface which has a fairly standard implementation, it's possible to provide the default implementation by creating an abstract base class. Say for example you defined a Sortable interface such as the following:

interface ISortable {
    public function sort($direction);
}

In most cases the code required to sort a collection is going to be the same. The only information that needs to be provided is the data to be sorted and how to compare that data.

What we can do then is provide a default implementation of sort() using one of many sorting algorithms and just separate out the data collection and comparison to separate functions. We declare those functions as abstract and let the subclass worry about the details.

class AbstractBubbleSort implements Sortable {
    public function sort($direction){
        $collection = $this->getCollection();
        if (count($collection) == 1){
            return $collection;
        }

        do {
            $hasSwapped=false;
            $keys = array_keys($collection);
            for ($i=0, $len=count($keys)-1; $i < $len; $i++){
                $a = $collection[$keys[$i]];
                $b = $collection[$keys[$i+1]];

                $ret = $this->compare($a, $b);
                if (($ret > 0 && $cirection == SORT_ASC) || ($ret < 0 && $direction == SORT_DESC)){
                    $hasSwapped = true;
                    $collection[$keys[$i+1]] = $a;
                    $collection[$keys[$i]] = $b;
                }
            }
        } while ($hasSwapped);

        return $collection;
    }

    abstract protected function compare();
    abstract protected function getCollection();
}

This abstract class implements a simple bubble sort. In order to implement the sort we need two bits of information that cannot be handled at this level. First we need to get the collection of data to be sorted. Second we need to be able to compare one piece of data to another. We handle these operations by moving them to a function which is marked as abstract.

A subclass can then implement only these two methods to have a functioning sort rather than having to implement their own complete sorting algorithm. For example if we wanted to create a subclass that can sort files based on their last modification time we might implement it like so:

class LastModifiedSorter extends AbstractBubbleSort {
    private $files;
    public function __construct($files){
        $this->files = $files;
    }

    protected function getCollection(){
        return $this->files;
    }

    protected function compare($a, $b){
        return filemtime($a)-filemtime($b);
    }
}

This class accepts an array of file names which can then be sorted by calling the sort method. Sort will get the list of files by calling the getCollection() method then apply the bubble sort algorithm to it. For each pair of files it will call the compare method to determine which order the files should be in.

In summary

Interfaces create a contract between two pieces of code. This contract allows the code to interact without having to know any specific details about how the other side works.

Interfaces are most useful when developing a system with swappable or extensible components. Code using the component only needs to know what the component is capable of doing, not how it does it.

Support free Wi-Fi

openwireless.org

Support free maps

openstreetmap.org