Fun with Array Interfaces

Tweet

As a programmer who works with different languages every day, I find a lot of joy in learning how things are done differently in other languages and seeing if I can do the same in PHP. One thing I liked in particular in Python was how one can emulate features of native data types in custom classes.

Take for example this members list class:

class Members:
    def __init__(self, members):
        self.members = members

    // other methods

By implementing the __iter__ method you could iterate over data in an instance of this class just as you would a list (array in PHP):

class Members:
    def __iter__(self):
        return self.members

ls = Members(["You", "Me"])

for member in members:
    print members

Testing for membership would simply entail implementing the __contains__ method:

class Members:
    def __contains__(self, member):
        return member in self.members

From there you could do:

"Me" in members:
    print "I am a member!"

I thought it would be nice if you could do the same in PHP on an instance of your custom classes and not only arrays:

isset($myObject["test"]);

PHP lets us do this with array interfaces.

Interfaces in a nutshell

Think of interfaces as contracts specifying methods that a class has to contain.

interface Multiplier {
    public function multiply($num1, $num2);
}

Any class using this interface must have such a multiply method. There is a keyword to signify that a class fulfills this contract: implements.

class SmartMultiplier implements Multiplier {
    public function multiply($num1, $num2) {
        return $num1*$num2;
    }
}

It doesn't matter how the contract is fulfilled as long as it is. An alternative way of implementing the multiply method can go like this:

class NotSoSmartMultiplier implements Multiplier {
    public function multiply($num1, $num2) {
        $product = $num1;
        foreach(range(1,$num2-1)) {
            $product = $num + $num2;
        }
        return $product;
    }
}

SPL and PHP Interfaces

PHP provides a library of predefined interfaces that can make our objects array-like by simply implementing them in the classes.

Some of these interfaces are included in the list of Predefined Interfaces and Classes and some in Standard PHP Library (SPL).

If those terms sound intimidating, don't let them. You've all used $_GET before. $_GET is a language construct that is said to be predefined.

On the other hand, from the documentation, SPL is just

a collection of interfaces and classes that are meant to solve common problems.

All that has to be done now is to see some of those interfaces in action. So let's dive in!

We are going to create a Twitter timeline class,

$tweets = new Timeline("jeunito");

able to count its tweets,

count($tweets);

loop through them,

foreach($tweets as $tweet) {
    echo $tweet; 
}

and get a tweet via a tweet id,

// get 
if (isset($tweets["some tweet id"])) {
    echo $tweets["some tweet id"];
}

just as we would in a normal array!

We have to get some things out of the way though. First create a Twitter account if you don't have one yet. Now sign up for a developer account and generate an access token and secret.

Next, download or clone the code from Github and run composer install inside the source folder. If you're unfamiliar with Composer, see SitePoint's previous article on it. Open the index.php file and add in the necessary Oauth data.

The Countable Interface

The Countable interface is probably the most self explanatory. It lets us pass objects to the count() method simply by implementing the count method.

We can get the number of tweets for a user by doing a GET request on "/users/show".

 public function count()
 {   
     $result = $this->api->get("users/show", array(
         'screen_name' => $this->username
     )); 

     return $result['statuses_count'];
}

The ArrayAccess Interface

We are now going to up the ante a bit by learning about a more interesting interface.

When implemented, ArrayAccess will enable our objects to be accessed like a map, which is really what they are. The methods to implement are

ArrayAccess {
    abstract public boolean offsetExists ( mixed $offset )
    abstract public mixed offsetGet ( mixed $offset )
    abstract public void offsetSet ( mixed $offset , mixed $value )
    abstract public void offsetUnset ( mixed $offset )
}

This is very handy in our Twitter timeline object. Testing if a tweet exists in a timeline would be done by passing our object to isset like so:

isset($tweets['some tweet id']);

To do that we simply perform a GET request on a tweet id.

public function offsetExists($offset) {
    $tweet = $this->api->get("statuses/show", array("id" => $offset));    
    if ($tweet) {
        return $tweet["text"];
    }   

    return false;
}

Even better, we can also use the above for offsetGet instead and let offsetExists call offset Get in turn.

public function offsetGet($offset) {
    $tweet = $this->api->get("statuses/show", array("id" => $offset));    
    if ($tweet) {
        return $tweet["text"];
    }   

    return false;
}

public function offsetExists($offset) {
    return $this->offsetGet($offset) !== false;
}

With offsetUnset we could also do a delete by tweet id, but I'll leave that up to you to implement.

Unfortunately, implementing offsetSet does not make much sense. For things like this, the easy way out is to just throw a custom exception like UnsupportedOperationException. But on the other hand, it may depend on your application's specific business rules as well.

The Iterator Interface

I have reserved the interface I like the most for last! The Iterator interface is extremely useful here because I don't think there is a better way to encapsulate the gory details of paging through a remote collection than looping through our timeline object as if it were a normal array.

First, the following methods need to be implemented:

Iterator extends Traversable {
    /* Methods */
    abstract public mixed current ( void )
    abstract public scalar key ( void )
    abstract public void next ( void )
    abstract public void rewind ( void )
    abstract public boolean valid ( void ) 
}

We could explicitly use methods above to loop through our timeline like so:

$it->rewind();

while ($it->valid())
{
    $key = $it->key();
    $value = $it->current();

    // do something

    $it->next();
}
?>

But why do that when you can do this:

foreach($tweets as $id => $tweet) {
    echo $tweet;
}

In our example, we are going to loop through the tweets in our timeline by chronologically retrieving chunks of tweets and then storing them in a buffer. We will iterate through this buffer until it runs out and then we got another batch of tweets using the id of the last tweet as offset.

Initially we have none and this is where the rewind method comes in: to get the latest 10 tweets so have an offset from where we can get the next 10.

public function rewind() 
{   
    $this->tweets = $this->api->get('statuses/user_timeline', array('count' => 20));
}

The valid() method is just there to indicate whether or not to continue looping. This can be done by checking if our buffer of tweets is emtpy:

public function valid()
{
    return !empty($this->tweets);
}

The key() and current() methods simply return the key and value of the current tweet in our iteration. For our purposes, we will simply get the tweet id and the text of the latest tweet from our buffer.

public function key() 
{
    $latest = reset($this->tweets);
    return $latest['id_str'];
}

public function current() 
{
     $latest = reset($this->tweets);
     return $latest['text'];
}

Finally there is the next method. Here we dequeue the head of our buffer to get the next element to iterate on. Then, we ensure we get the next set of tweets if we are at the last element of our buffer.

public function next()
{
    $head = array_shift($this->tweets);
    if (!is_null($head) && empty($this->tweets))
    {
        $this->tweets = $this->api->get('statuses/user_timeline', array('count' => 800, 'max_id' => $head['id_str']));
    }
}

We are done! That was a very basic implementation of looping through a user's tweets. There is a lot more that can be done like caching locally to save on api calls but that's the beauty of using interfaces: they allow us to change our strategy under the hood and as long as our implementation is still correct, we can expect it to still work.

But for now, you can watch our timeline object working hard by running php index.php at the command line.

Conclusion

The benefit of Interfaces is two-fold. They let us encapsulate implementation details and afford us syntactic sugar, both of which can be a real charm in any application that asks for interoperability. If you have any questions or comments, please leave them in the comments section below!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • HotDang

    Interfaces are also very important both for hinting object composition and for inheritance because you can implement more than one interface.

  • http://metalmatze.de/ MetalMatze

    Hey, nice article so far. I’m currently working through it. After running composer install you say that I should put my OAuth information into the Timeline.php but it’s actually in the index.php ;)

    • Jeune Asuncion

      Yeah that was a typo. Let me get that fixed.

  • sebastiaan hilbers

    Instead of implementing a plain ‘ol “Iterator” you could implement “IteratorAggregate” with a “ArrayIterator” which is composed of tweets.

    Also, you’re calling offsetGet without the offset in offsetExists. Typo?

    • Jeune Asuncion

      You could surely do that but I wanted to focus on interfaces. Fixed the typo btw :)

  • sebastiaan hilbers

    PHP supports traits and we can choose composition over inheritance too!