Home arrow PHP arrow Page 3 - The Iterator Pattern

Sample Code - PHP

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

TABLE OF CONTENTS:
  1. The Iterator Pattern
  2. Extending Lendable
  3. Sample Code
  4. A Variant Iterator API
By: php|architect
Rating: starstarstarstarstar / 10
November 23, 2005

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

The first step in implementing the GoF Iterator pattern within Library is to write a new test case for the new concrete Iterator. Since each test method will manipulate a Library filled with Media instances, you can employ the UnitTestCase::setUp() method to populate a variable with a Library in a known state for each test.

Start by adding the Library::getIterator() method as a Factory for instances of the LibraryGofIterator class.

  class IteratorTestCase extends UnitTestCase {
   
protected $lib;
   
function setup() {
     
$this->lib = new Library;
     
$this->lib->add(new Media('name1', 2000));
     
$this->lib->add(new Media('name2', 2002));
     
$this->lib->add(new Media('name3', 2001));
   
}
   
function TestGetGofIterator() {
     
$this->assertIsA($it = $this->lib->getIterator()
        ,'LibraryGofIterator');
   
}
  }

Here's the implementation:

  class Library {
   
// ...
   
function getIterator() {
     
return new LibraryGofIterator($this->collection);
   
}
 
}

The getIterator() method passes the Library's $collectionto the constructor of the new concrete iterator. This technique has twoimportant implications: each iterator is independent, so multipleiterators can operate at the same time. Additionally, the iteratoroperates on the collection as it existed at the time theiterator was requested. If another item is added to the collection atany time later, you must request another iterator to display it (atleast in this implementation).

Let's continue enhancing the test suite by adding assertions to the TestGetGofIterator() method to match the Iterator design pattern. The isDone() method should only be true if you've iterated over the entire collection. If the iterator's just been created, isDone() should obviously return false to indicate it's okay to iterate.

  class IteratorTestCase extends UnitTestCase {
   
function setup() { /* ... */ }
   
function TestGetGofIterator() {
     
$this->assertIsA($it = $this->lib->getIterator()
        ,'LibraryGofIterator');
     
$this->assertFalse($it->isdone());
   
}
 
}

As usual with TDD, implement the simplest possible code that satisfies your test case:

  class LibraryGofIterator {
   
function isDone() {
     
return false;
   
}
 
}

So, what should happen during the first iteration?currentItem() should return the first Media object added in the IteratorTestCase::setUp() method and isDone() should continue to be false, since two additional items remain to be iterated over.

  class IteratorTestCase extends UnitTestCase {
   
function setup() { /* ... */ }
   
function TestGetGofIterator() {
     
$this->assertIsA($it = $this->lib->getIterator()
        ,'LibraryGofIterator');
     
$this->assertFalse($it->isdone());
     
$this->assertIsA($first = $it->currentItem(), 'Media');
     
$this->assertEqual('name1', $first->name);
     
$this->assertFalse($it->isdone());
   
}
 
}

It's critical that LibraryGofIterator receives the $collection in the constructor (see the minimal implementation of Library above) and returns the current() item of that array from the currentItem()method.

  class LibraryGofIterator {
   
protected $collection;
   
function __construct($collection) {
     
$this->collection = $collection;
   
}
   
function currentItem() {
     
return current($this->collection);
   
}
   
function isDone() {
     
return false;
   
}
 
}

What should happen in the next iteration? The next()method should change what item is returned by the currentItem() method. This next test captures that expected behavior:

  class IteratorTestCase extends UnitTestCase {
   
function setup() { /* ... */ }
   
function TestGetGofIterator() {
     
$this->assertIsA($it = $this->lib->getIterator(), 'LibraryGofIterator');
     
$this->assertFalse($it->isdone());
     
$this->assertIsA($first = $it->currentItem(), 'Media');
     
$this->assertEqual('name1', $first->name);
     
$this->assertFalse($it->isdone());
     
$this->assertTrue($it->next());
     
$this->assertIsA($second = $it->currentItem(), 'Media');
     
$this->assertEqual('name2', $second->name);
     
$this->assertFalse($it->isdone());
   
}
 
}

Piggybacking again on PHP's array functions, use next() on the array:

  class LibraryGofIterator {
   
protected $collection;
   
function __construct($collection) {
     
$this->collection = $collection;
   
}
   
function currentItem() {
     
return current($this->collection);
   
}
   
function next() {
     
return next($this->collection);
   
}
   
function isDone() {
     
return false;
   
}
 
}

The third iteration looks much like the others, except the isDone() method must return true. You also want next() to indicate success of moving to the next iteration:

  class IteratorTestCase extends UnitTestCase {
    
function setup() { /* ... */ }
   
function TestGetGofIterator() {
      
$this->assertIsA($it = $this->lib->getIterator(), 'LibraryGofIterator');
     
$this->assertFalse($it->isdone());
      
$this->assertIsA($first = $it->currentItem(), 'Media');
     
$this->assertEqual('name1', $first->name);
     
$this->assertFalse($it->isdone());
      
$this->assertTrue($it->next());
     
$this->assertIsA($second = $it->currentItem(), 'Media');
     
$this->assertEqual('name2', $second->name);
     
$this->assertFalse($it->isdone());
     
$this->assertTrue($it->next());
     
$this->assertIsA($third = $it->currentItem(), 'Media');
     
$this->assertEqual('name3', $third->name);
     
$this->assertFalse($it->next());
     
$this->assertTrue($it->isdone());
   
}
 
}

With small modifications to the next() and isDone()methods, all of the tests pass Here's the code so far:

  class LibraryGofIterator {
   
protected $collection;
   
function __construct($collection) {
     
$this->collection = $collection;
    
}
   
function first() {
      
reset($this->collection);
   
}
   
function next() {
     
return (false !== next($this->collection));
   
}
   
function isDone() {
     
return (false === current($this->collection));
   
}
   
function currentItem() {
     
return current($this->collection);
   
}
 
}

There's just one problem with the Iterator test case: it doesn't reflect how iterators are typically used. Yes, it tests all of the features of the Iterator pattern, but application code uses the Iterator in a much simpler way. So, the next step is to write a test using more realistic code.

  class IteratorTestCase extends UnitTestCase {
   
protected $lib;
   
function setup() { /* ... */ }
   
function TestGetGofIterator() { /* ... */ }
    function TestGofIteratorUsage() {
     
$output = '';
     
for ($it=$this->lib->getIterator(); !$it->isDone(); $it->next()){
       
$output .= $it->currentItem()->name;
     
}
     
$this->assertEqual('name1name2name3', $output);
   
}
 
}

So far, the implementation of Iterator copies an array (the collection) and uses PHP's internal pointer to track the iteration. You can also implement the Iterator by keeping track of the collection index by yourself. This requires a new accessor method in Library to fetch an object by key.

  class Library {
   
// ...
   
function get($key) {
     
if (array_key_exists($key, $this->collection)) {
       
return $this->collection[$key];
     
}
   
}
 
}

Also, you'd pass $this (the library itself) to the constructor instead of $this->collection (the array containing the Media collection) in the Library::getIterator() method.

The "external" iterator would then just track a pointer internally to know which element of the Library collection it's currently referencing, and would use the reference to the Library passed in the constructor to call the get() method to retrieve the current object.

  class LibraryGofExternalIterator {
   
protected $key = 0;
   
protected $collection;
   
function __construct($collection) {
     
$this->collection = $collection;
   
}
   
function first() {
     
$this->key=0;
   
}
   
function next() {
     
return (++$this->key < $this->collection->count());
   
}
   
function isDone() {
     
return ($this->key >= $this->collection->count());
   
}
   
function currentItem() {
     
return $this->collection->get($this->key);
   
}
 
}

This implementation assumes your collection array is indexed starting with 0 and is completely sequential.



 
 
>>> 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: