This article, the first of two parts, explains how to use the Iteratorpattern to manipulate any collection of objects. It is excerpted fromchapter eight of the book PHP|architect's Guide to PHP Design Patterns, written by Jason E. Sweat (PHP|architect, 2005; ISBN: 0973589825).
While the foregoing code is a complete implementation of the Iterator pattern as described by GoF, you may find the four-method API a bit cumbersome. If so, you can collapse next(), currentItem(), and isDone()into justnext()by having the latter either advance and return the current item fromthe collection or return false if the entire collection has beenprocessed.
Here's one way to write a test for this variation of the API:
In the code above, notice the simplified control structure for looping. next() returns an object or false, allowing you to perform the assignment inside the while loop conditional.
The next few examples explore variations of the Iterator pattern using the smaller interface. As a convenience, change the Library::getIterator() method to a parameterized Factory so you can get either the four-method iterator or the two-method iterator (next() and reset()) from that single method.
class Library { // ... function getIterator($type=false) { switch (strtolower($type)) { case 'media': $iterator_class = 'LibraryIterator'; break; default: $iterator_class = 'LibraryGofIterator'; } return new $iterator_class($this->collection); } }
Here, Library::getIterator() now acceptsa parameter to select what kind of iterator to return. The default isLibraryGofIterator (so the existing tests still pass). Passing thestring media to the method creates and returns a LibraryIterator instead.
This is some code to implement LibraryIterator:
class LibraryIterator { protected $collection; function __construct($collection) { $this->collection = $collection; } function next() { return next($this->collection); } }
Oops! The dreaded red bar! What happened to get the error "Equal expectation fails at character 4 with name1name2name3 and name2name3"? Somehow, the first iteration was skipped—that's a bug. To fix the error, return current() for the first call of the next()method.
class LibraryIterator { protected $collection; protected $first=true; function __construct($collection) { $this->collection = $collection; } function next() { if ($this->first) { $this->first = false; return current($this->collection); } return next($this->collection); } }
Presto! A green bar and a streamlined while loop iterator.