Home arrow PHP arrow Page 3 - The Iterator Pattern, concluded

SPL Iterator - PHP

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).

TABLE OF CONTENTS:
  1. The Iterator Pattern, concluded
  2. Sorting Iterator
  3. SPL Iterator
  4. Issues
By: php|architect
Rating: starstarstarstarstar / 5
December 01, 2005

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

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.



 
 
>>> More PHP Articles          >>> More By php|architect
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: