Key Takeaways
- PHP’s array interfaces allow programmers to emulate features of native data types in custom classes, similar to Python’s methods. This enables custom classes to behave like arrays, and allows for the use of common array operations such as counting elements, looping through elements, and accessing elements via index.
- Interfaces are like contracts for classes, specifying methods that a class must contain. They allow for encapsulation of implementation details and provide syntactic sugar, improving code readability and maintainability. PHP provides a library of predefined interfaces that can be implemented to make objects array-like.
- The Countable, ArrayAccess, and Iterator interfaces in PHP allow objects to pass the count() method, be accessed like a map, and iterate over collections respectively. These interfaces can be used to create more dynamic and interactive objects, such as a Twitter timeline class that can count its tweets, loop through them, and access a tweet via its id.
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!
Jeune has a fascination for building and solving things. Right now, he is interested in data mining, algorithms, performance, and software design. When not in front of his laptop, he cooks mean Asian dishes or is out kicking ass on the football (soccer) field.