Using SPL Iterators, Part 2

This entry is part 2 of 2 in the series Using SPL Iterators

Using SPL Iterators

In part one of this series I introduced you to some of the SPL Iterators and how to use them. There may be times however when the provided iterators are insufficient for your needs and you’ll want to roll your own custom iterator. Luckily, SPL provides interfaces that will allow you to do just that.

For an object to be traversable in a foreach loop, PHP requires that it be an instance of Traversable. You cannot however implement this interface directly (though you can use it in instaceof checks); instead you’ll need to implement either SPL’s Iterator or IteratorAggregate interfaces.

Iterator

Iterator allows you to create objects that are are iterated directly or for the creation of external Iterators. It specifies five methods that need to be implemented: rewind(), current(), key(), next(), and valid().

Assume we have a library of books and we want to be able to iterate over the collection of books in the library. Here’s an example that does so by implementing Iterator:

<?php
class Library implements Iterator
{
    // an internal pointer to the current position
    // in the dataset
    protected $position = 0;

    // an array of the titles of the books in the library
    protected $books = [
        "Professional PHP Programming",
        "Programming Perl",
        "A Byte of Python",
        "The Ruby Way"
    ];

    // this method takes the pointer back to the beginning
    // of the dataset to restart the iteration
    public function rewind() {
        echo "rewinding <br>";
        $this->position = 0;
    }

    // this method returns the value at the current
    // position of the dataset
    public function current() {
        echo "current <br>";
        return $this->books[$this->position];
    }

    // this should return the current value of the pointer
    public function key() {
        echo "key <br>";
        return $this->position;
    }

    // this method moves the pointer to the next value
    // in the data set
    public function next() {
        echo "next <br>";
        ++$this->position;
    }

    / /this returns a boolean indicating if the there
    // is data at the current position in the dataset
    public function valid() {
        echo "valid <br>";
        return isset($this->books[$this->position]);
    }
}

$library = new Library();
foreach ($library as $key => $value) {
    echo $key . ": " . $value . "<br>";
}

The output of the above code is:

rewinding 
valid 
current 
key 
0: Professional PHP Programming
next 
valid 
current 
key 
1: Programming Perl
next 
valid 
current 
key 
2: A Byte of Python
next 
valid 
current 
key 
3: The Ruby Way
next 
valid

When the iteration starts, PHP calls the rewind() method to take the pointer back to the beginning of the dataset. It then checks to see if there is valid data at that point by calling valid(). If it receives true, it then calls current() to get the value at that point in the iteration. Since you are asking for the $key in the loop construct, PHP calls the key() method to get the value of the current key. PHP then calls next() to advance the pointer and calls valid() again to check if there is valid data. The cycle continues until valid() returns false.

Iterator gives you the ability to implement very powerful iterators from scratch and is best suited for creating internal iterators like above. Of course, the example above is simple and is only intended to give you an overview of the required methods and how they work. Ideally, with a dataset such as the one above (an array), you will be better off using IteratorAggregate.

IteratorAggregate

IteratorAggregate requires you to implement a single method, getIterator(). This method is required to return an external iterator which will be used for iteration. Rewriting the example above to use IteratorAggregate gives us:

<?php
class Library implements IteratorAggregate
{
    protected $books = [
        "Professional PHP Programming",
        "Programming Perl",
        "A Byte of Python",
        "The Ruby Way"
    ];

    // return an Iterator of your data
    public function getIterator() {
        echo "getIterator <br>";
        return new ArrayIterator($this->books);
    }
}

$library = new Library();
foreach($library as $key => $value) {
    echo $key . ": " . $value . "<br>";
}

The output of the above code is:

getIterator 
0: Professional PHP Programming
1: Programming Perl
2: A Byte of Python
3: The Ruby Way

At the start of the iteration, PHP calls getIterator() which returns an iterator, in this case SPL’s ArrayIterator. PHP then makes all the appropriate calls on that iterator. Basically, you hand over the data to the external iterator which does all the work for you while you sit back and relax.

Since both interfaces provide the same functionality, knowing whether to use Iterator or IteratorAggregate can sometimes be tricky. As a general rule of thumb, you should use Iterator when you need to actually define the behavior of your iterator from scratch because IteratorAggregate is not a good fit.

Summary

I’ve introduced you to two interfaces that allow you to create your own iterators in this article. Hopefully, you understand how easy it is to create your very own iterators and are ready to add this cool feature of SPL to your arsenal. Of course, if you feel I missed something or erred in any way, or even just have something to share, please don’t hesitate to let me know in the comments!

Image via Mushakesa / Shutterstock

Using SPL Iterators

<< Using SPL Iterators, Part 1

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • http://www.maltblue.com Matthew Setter

    Hi Stefan,

    Nice introductory post on Iterators mate. They’re definitely one of my favourite topics and an aspect of PHP that really provide the ability to have a lot of power and flexibility whilst still retaining the use of common lexicographical constructs like for and foreach etc. Which other iterators are you going to be covering? Will there be reference to Filter and Limit Iterator?

    Matt

    • http://www.maltblue.com Matthew Setter

      Stefan,

      please excuse my writing in haste and not remembering you covering these in part one.

      Matt

      • http://careers.stackoverflow.com/frostymarvelous Stefan Froelich

        Don’t worry about it. I am quite pleased you enjoyed the article. This gives me the confidence to write more. Thanks.

  • http://danapplegate.github.com Dan Applegate

    Awesome. Clean, concise, clear examples that really demonstrate the ideas behind when iterators are useful. Any plans to write more articles about the SPL classes? Their documentation is lacking, and I’m still very unaware of what they are all used for…

    • http://careers.stackoverflow.com/frostymarvelous Stefan Froelich

      Hello Dan,

      No plans in the foreseeable future, though I am planning to write about generators.
      Do you have any ideas about what you would want me to write about?