SPL: Using the iteratorAggregate interface
Tagged with: [ iterators ] [ PHP ] [ spl ]
The SPL is one of hardest things to grasp for most PHP developers. But why is this? The lack of documentation inside the
manual, the fact that there are not many real-life examples, or maybe it’s just too hard? In this post I will try to
explain a bit more about the “iteratorAggregate”
interface. Together with its more famous brother Iterator
, they are currently the two only implementations of the
Traversable
interface, which is needed for objects so they can be used within a standard foreach()
loop. But why and
when should we use the iteratorAggregate
?
iteratorAggregate, because not everything is an iterator
Let’s say we have a book, and that books contains chapters. As a good-guy OO programmer, you probably will define a Book class and a separate Chapter class to deal with this. So is our Book-class an iterator? Well, it would be nice to be able to iterate over its chapters, so yeah, it could be an iterator because we could use something like this:
Plus the fact that we are an iterator, we can do everything we could do with every other type of iterator. In the end, we can even throw in a library as well, which itself is an iterator (of a collection) of books:
But in order to achieve all this, we must implement the iterator
interface and deal with the iterator’s key()
,
current()
, valid()
, rewind()
and next()
methods inside our book class. We don’t really want this because this is
not really the concern of the Book
class. The Book
class should just deal with the book and it should not contain methods
for iterating chapters. We really like to move that somewhere else but on the other hand, we cannot really move it to
our Chapter
class since it would mean the Chapter would need to deal with multiple chapters. The Chapter
class just
deals with a single chapter.
So we will be introducing the iteratorAggregate
which sounds far more complex than it actually is: The
iteratorAggregate
interface lets us deal with “aggregating” objects. The interface defines a single method called
getIterator()
, who’s only job is to return an iterator. One of the advantages is that an object that implemented
IteratorAggregate
, itself can be used through foreach()
, just the same way a normal iterator can. Foreach()
takes care
of finding the actual iterator by calling the object’s getIterator()
method.
As you can see we define two classes: Book
and Chapter
. The Chapter
class by itself isn’t capable of iterating. This is
not needed since we let our book deal with this. The book class implements iteratorAggregate
. Now, since we didn’t
implement iterator
, but iteratorAggregate
, we don’t need to implement our key()
, valid()
, next()
, current()
,
rewind()
methods. We only need a getIterator()
method that will return the actual iterator. In our case, it’s an
arrayIterator
over our chapters, since our chapters are stored as Chapter
objects inside an array.
At this point we have separated the concerns of the classes and have a “clean” Book
and Chapter
class. Makes things
easier, testable and in the end, more maintainable.
Further improvements
Where should we add a method on how many chapters there are inside the book? We could add a getChapterCount()
method
to our Book
class, or even let it implement countable
so we could use PHP’s standard count()
function for it:
Since we now have an iterator, it’s easy to add our own filtering, for instance, limiting the foreach()
to the first 2
chapters:
In a sense, the iteratorIterator does nothing really much more than stop letting you worry about if you are supplying an Iterator or an IteratorAggregate, since most filters need an iterator as input (and obviously, our $book isn’t. It’s easy to extend functionality this way without even having to touch chapters or book objects. It’s easy to setup, it’s maintainable and we can test our code without any problem.
« Thanks to Stephan Hochdörfer for some help on the examples »