The Iterator Pattern, concluded

This article, the second of two parts, explains how to use the Iterator pattern to manipulate any collection of objects. It is excerpted from chapter eight of the book PHP|architect’s Guide to PHP Design Patterns, written by Jason E. Sweat (PHP|architect, 2005; ISBN: 0973589825).

 Filtering Iterator

With Iterators,you can do more than just present each item of the collection. You can also select what items are presented. Let’s modify the Library::getIterator() to allow two additional iterator types.

  class Library {
   
// …
   
function getIterator($type=false) {
     
switch (strtolower($type)) {
     
case ‘media':
       
$iterator_class = ‘LibraryIterator';
       
break;
     
case ‘available':
       
$iterator_class = ‘LibraryAvailableIterator';
       
break;
     
case ‘released':
       
$iterator_class = ‘LibraryReleasedIterator';
       
break;
     
default:
       
$iterator_class = ‘LibraryGofIterator';
     
}
     
return new $iterator_class($this->collection);
   
}
 
}

The class LibraryAvailableIterator should only iterate over items that have a status of “library” (recall that the checkOut() method changes the status to “borrowed”).

  class IteratorTestCase extends UnitTestCase {
   
// …
   
function TestAvailableIteratorUsage() {
     
$this->lib->add($dvd = new Media(‘test’, 1999));
     
$this->lib->add(new Media(‘name4′, 1999));
     
$this->assertIsA(
       
$it = $this->lib->getIterator(‘available’)
       
,’LibraryAvailableIterator’);
     
$output = ”;
    
  while ($item = $it->next()) {
       
$output .= $item->name;
     
}
     
$this->assertEqual(‘name1name2name3testname4′, $output);
      $dvd->checkOut(ÔJasonÕ);
     
$it = $this->lib->getIterator(‘available’);
     
$output = ”;
     
while ($item = $it->next()) {
       
$output .= $item->name;
     
}
     
$this->assertEqual(‘name1name2name3name4′, $output);
   
}
 
}

This test creates a new Media instance and stores it in the variable $dvd. The first highlighted assertEqual()assertion verifies that the new item is present when iterating with LibraryAvailableIterator. Next, the test uses the checkOut() method and verifies that the new item is missing from the display.

The code to implement filtering is very similar to LibraryIterator::next(), except filtering is done prior to returning the item. If the current item does not match the filter criteria, the code returns $this->next() instead.

  class LibraryAvailableIterator {
   
protected $collection = array();
   
protected $first=true;
   
function __construct($collection) {
     
$this->collection = $collection;
    }
    
function next() {
      
if ($this->first) {
       
$this->first = false;
       
$ret = current($this->collection);
      
} else {
        
$ret = next($this->collection);
     
}
      
if ($ret && ‘library’ != $ret->status) {
        
return $this->next();
      
}
      
return $ret;
   
}
 
}

{mospagebreak title=Sorting Iterator}

An iterator can do more than show all or a portion of the collection. An iterator can also show the collection in a specific order. Let’s create an iterator that sorts the Media in the collection by release date.

For a test, add some Media instances with dates older that those of the items added in the setUp() method. If the iterator works, these older items should be sorted to the beginning of the iteration.

  class IteratorTestCase extends UnitTestCase {
   
// …
    
function TestReleasedIteratorUsage() {
     
$this->lib->add(new Media(‘second’, 1999));
     
$this->lib->add(new Media(‘first’, 1989));
     
$this->assertIsA(
        
$it = $this->lib->getIterator(‘released’)
        ,’LibraryReleasedIterator’);
     
$output = array();
     
while ($item = $it->next()) {
       
$output[] = $item->name .’-‘.  $item->year;
     
}
     
$this->assertEqual(
        ‘
first-1989 second-1999 name1-2000 name3-2001 name2-2002′
        ,implode(”,$output));
   
}
 
}

This test uses the items in each iteration slightly differently: instead of just appending the $name values in a string, a string is formed from both the $name and $year properties, which is then appended to an $output array.

The implementation of LibraryReleasedIterator is nearly identical to LibraryIterator, except for one additional line in the constuctor:

  class LibraryReleasedIterator extends LibraryIterator {
   
function __construct($collection) {
     
usort($collection, create_function(‘$a,$b’,’return ($a->year – $b->year);’));
     
$this->collection = $collection;
   
}
 
}

The line in bold sorts the $collection array prior to iteration. You can avoid copying all of the other code for the class by simply inheriting from the LibraryIterator class itself.

Is it possible to use an external iterator to accomplish this same sorted iteration? Yes, but you must pull a few tricks to accomplish it.

  class LibraryReleasedExternalIterator {
   
protected $collection;
   
protected $sorted_keys;
   
protected $key=-1;
   
function __construct($collection) {
     
$this->collection = $collection;
     
$sort_funct = create_function(
        ‘
$a,$b,$c=false’,
        ‘
static $collection;
       
if ($c) {
         
$collection = $c;
         
return;
       
}
       
return ($collection->get($a)->year -

          $collection->get($b)->year);’);
     
$sort_funct(null,null,$this->collection);
     
$this->sorted_keys = $this->collection->keys();
     
usort($this->sorted_keys, $sort_funct);
    
}
   
function next() {
     
if (++$this->key >= $this->collection->count()) {
       
return false;
     
} else {
       
return $this->collection->get($this->sorted_keys[$this->key]);
     
}
   
}
 
}

Key here is the creation of a utility function for performing the sort. The sorting function needs to have access to the collection so it can fetch members for comparison. However, because the generated function is used in ausort(), you don’t have the option of passing the collection as an additional parameter. Instead, you can use the trick shown in the code block above to store a reference to the collection inside the function prior to calling it with usort().

What you’re sorting is the list of keys for the collection. When usort() is complete, the keys will be sorted in order by the year attribute of each object in the collection.

In the next() method, an object in the collection is accessed via the get() method, but indirectly through the $sorted_keys mapping. If you recall the external version of the GoF-style iterator, arrays with gaps or strings in the keys could be problematic. This same trick could be used for a simple external iterator to alleviate the problem of gaps in the sequence of keys.

{mospagebreak title=SPL Iterator}

No chapter on the Iterator design pattern and PHP would be complete without discussing the “Standard PHP Library” (SPL) iterator.

The while loop structure used so far is very compact and usable, but PHP coders may be more comfortable with the foreach structure for array iteration. Wouldn’t it be nice to use a collection directly in a foreach loop? That’s exactly what the SPL iterator is for.

(Even though this chapter has been written entirely for PHP5, the following SPL code is the only code that works solely in PHP5, and then only if you’ve compiled PHP 5 with SPL enabled.) Harry Fuecks wrote a nice article introducing the SPL and covering the SPL iterator; see http://www.sitepoint.com/article/php5-standard-library.

Using SPL is essentially a completely different way to implement iteration, so let’s start over with a new unit test case and a new class, the ForeachableLibrary.

  class SplIteratorTestCase extends UnitTestCase {
   
protected $lib;
   
function setup() {
      
$this->lib = new ForeachableLibrary;
     
$this->lib->add(new Media(‘name1′, 2000));
     
$this->lib->add(new Media(‘name2′, 2002));
     
$this->lib->add(new Media(‘name3′, 2001));
   
}
   
function TestForeach() {
     
$output = ”;
     
foreach($this->lib as $item) {
       
$output .= $item->name;
     
}
     
$this->assertEqual(‘name1name2name3′, $output);
   
}
 
}

ForeachableLibrary is the collection that implements the SPL Iterator interface. You have to implement five functions to create an SPL iterator: current(), next(), key(), valid(), and rewind(). key() returns the current index of your collection. rewind() is like reset(): iteration restarts at the start of your collection.

  class ForeachableLibrary
      
extends Library
     
implements Iterator {
   
protected $valid;
   
function current() {
     
return current($this->collection);
   
}
   
function next() {
     
$this->valid = (false !== next($this->collection));
   
}
   
function key() {
     
return key($this->collection);
   
}
   
function valid() {
     
return $this->valid;
   
}
   
function rewind() {
     
$this->valid = (false !== reset($this->collection));
   
}
 
}

Here, the code we just implements the required functions working on the $collection attribute. (If you don’t implement all five functions and you add the implements Iterator to your class definition, PHP will generate a fatal error.) The tests are “green,” so everything is happy.

There’s just one problem: the implementation is limited to one style of iteration—sorting or filtering is impossible.

Can anything be done to rectify this? Yes! Apply what you learned from the Strategy pattern (see Chapter 7) and delegate the SPL iterator’s five functions to another object.

This is a test for PolymorphicForeachableLibrary.

  class PolySplIteratorTestCase extends UnitTestCase {
   
protected $lib;
   
function setup() {
     
$this->lib = new PolymorphicForeachableLibrary;
     
$this->lib->add(new Media(‘name1′, 2000));
     
$this->lib->add(new Media(‘name2′, 2002));
     
$this->lib->add(new Media(‘name3′, 2001));
   
}
   
function TestForeach() {
     
$output = ”;
     
foreach($this->lib as $item) {
       
$output .= $item->name;
     
}
     
$this->assertEqual(‘name1name2name3′, $output);
   
}
 
}

The only difference between this case and the test for SplIteratorTestCase is the class of the
$this->lib attribute created in the setUp() method. That makes sense: the two classes must behave identically.

Here’s PolymorphicForeachableLibrary.

  class PolymorphicForeachableLibrary
     
extends Library
     
implements Iterator {
   
protected $iterator;
   
function current() {
     
return $this->iterator->current();
   
}
   
function next() {
     
return $this->iterator->next();
   
}
   
function key() {
     
return $this->iterator->key();
   
}
   
function valid() {
     
return $this->iterator->valid();
   
}
   
function rewind() {
     
$this->iterator =
       
new StandardLibraryIterator($this->collection);
     
$this->iterator->rewind();
   
}
 
}

Library is extended to get the collection manipulation methods. The SPL methods are added, too, all delegating to the $iterator attribute, which is created in rewind(). Below is the code for the StandardLibraryIterator.

  class StandardLibraryIterator {
   
protected $valid;
   
protected $collection;
   
function __construct($collection) {
     
$this->collection = $collection;
   
}
   
function current() {
     
return current($this->collection);
   
}
   
function next() {
     
$this->valid = (false !== next($this->collection));
   
}
   
function key() {
     
return key($this->collection);
   
}
   
function valid() {
     
return $this->valid;
   
}
   
function rewind() {
     
$this->valid = (false !== reset($this->collection));
   
}
 
}

This code should look familiar: essentially, it’s a copy of the five SPL functions from the ForeachableLibrary class. The tests pass.

OK, the code is more complex now, but how does it support additional iterator types? Let’s add a test for a “released” version of the iterator to see how additional iterator types work in this design.

  class PolySplIteratorTestCase extends UnitTestCase {
   
// …
    function TestReleasedForeach() {
     
$this->lib->add(new Media(‘second’, 1999));
     
$this->lib->add(new Media(‘first’, 1989));
     
$output = array();
     
$this->lib->iteratorType(‘Released’);
     
foreach($this->lib as $item) {
       
$output[] = $item->name .’-‘. $item->year;
     
}
     
$this->assertEqual(
        ‘
first-1989 second-1999 name1-2000 name3-2001 name2-2002′
        ,implode(‘ ‘,$output));
   
}
 
}

This test case above should look familiar, too, as it’s very similar to the previous “release” iterator, but using the foreach control structure to loop.

  class PolymorphicForeachableLibrary
   
extends Library
   
implements Iterator {
   
protected $iterator_type;
   
protected $iterator;
   
function __construct() {
     
$this->iteratorType();
   
}
   
function iteratorType($type=false) {
     
switch(strtolower($type)) {
     
case ‘released':
       
$this->iterator_type = ‘ReleasedLibraryIterator';
       
break;
     
default:
       
$this->iterator_type = ‘StandardLibraryIterator';
      }     

     
$this->rewind();
   
}
   
// …
   
function rewind() {
     
$type = $this->iterator_type;
      
$this->iterator = new $type($this->collection);
     
$this->iterator->rewind();
   
}
 
}

The new iteratorType() method lets you switch which style of iterator you want to use. (Since the iterator type isn’t chosen during the instantiation of the object and because you can choose a different iterator type on-the-fly by calling the iteratorType() method again, the code is actually implementing the State pattern, rather than the Strategy pattern.)

  class ReleasedLibraryIterator
   
extends StandardLibraryIterator {
   
function __construct($collection) {
     
usort($collection
      
,create_function(‘$a,$b’,’return ($a->year – $b->year);’));
     
$this->collection = $collection;
   
}
 
}

You can easily implement ReleasedLibraryIterator by extending StandardLibraryIterator and overriding the constructor to add the sorting of the incoming array. And with that you have a working PolymorphicForeachableLibrary.

{mospagebreak title=Issues}

Iterators are a nice way to standardize working with collections of objects in your applications. The examples here have been based on arrays, but the ability to work on non-array based collections with an identical interface is powerful.

The ability to use collections in the foreach control structure is indeed cool. The only unfortunate issue with the SPL implementation is the significant potential for name space clashing with “Iterator”.  How much PHP4 object-oriented code has some sort of an Iterator class as a base class for the libraries’ iterators? Of those, how many define the five required methods in the same capacity? Perhaps implements Foreachable would have been a less intrusive name. If you choose to use the SPL, you should investigate the other supported iterators, like RecursiveArrayIterator and numerous other flavors.

[gp-comments width="770" linklove="off" ]

chat