Patterns For PHP
Index Patterns News Forums

Iterator

From Patterns For PHP

Contents

Introduction

PHP has a history of using functions for iteration, but there is now a push to use objects in PHP 5. The Iterator Pattern is not language specific and doesn't need any language enhancements to create. The added benefit in PHP 5 is that objects are allowed in foreach loops with the SPL Iterator interface.

The Iterator Pattern is not constrained to the class which holds the data and can aggregate the iterator implementation to another object. The aggregated class provides iteration implementation separate from the main class. Aggregation of the Iterator is the best practice for optimizing overhead.

The Iterator Pattern allows you to control how the data is passed to the developer in the loop and what methods they call. The pattern is a standard that creates a smoother transition and easier for the developer. They will know which methods to call for an action without having to look up method name in your reference manual.

When using the Iterator Pattern, you should only collect the information that you need.

Definition

The Iterator Pattern allows for accessing the current element and allowing the retrieval of additional elements in a loop routine or manually in a code block.

Implementing the Iterator Pattern

This function will create an array and return it for testing the Iterators.

php
function getPageUser($page)
{
    $list = array();
    for ($i = 97; $i < 117;   $i) {
        $user = new User();
        $user->name = str_repeat(chr($i), 6);
        $list[] = $user;
    }
    
    return $list;
}

For the Iterator object, we are going to get the full list and also allow for multiple pages implementation. This object uses mysql queries to get user list and go through the list.

php
class UserListMysql implements Iterator
{
    protected $query = null;
    private $row = null;
 
    function __construct($page = 1, $limit = 20)
    {        
        $this->query = mysql_query("SELECT * FROM users ORDER BY username LIMIT ". ($page-1) .", $limit");
    }
 
    public function rewind()
    {
        $this->row = mysql_data_seek($this->query, 0);
        return true;
    }
 
    public function next()
    {
        $this->row = mysql_fetch_assoc($this->query);
        return $this->row;
    }
 
    public function valid()
    {
        if($this->row == false)
        {
            mysql_free_result($this->query);
            return false;
        }
 
        return true;
    }
 
    public function current()
    {
        return $this->row;
    }
 
    // Not Used.
    public function key()
    {
        return true;
    }
}

To use the Iterator, it easy if you use the foreach loop.

php
foreach(new UserList(1) as $row)
{
    print_r($row);
}

There is also an array version for doing a similar action. It will work with any mysql fetched array, or a user created array.

php
class ArrayPagedList implements Iterator
{
 
   protected $_page;
 
   protected $_limit;
 
   protected $_list;
 
   private $_at = 1;
 
   public function __construct($list, $page = 1, $limit = 20)
   {
       if($page == 1) {
           $this->_list = $list;
       } else {
           $offset = $page * $limit;
           $this->_list = @array_slice($list, $offset, 20);
       }
       $this->_page = $page;
       $this->_limit = $limit;
   }
 
   public function rewind()
   {
       $this->_at = 1;
       return reset($this->_list);
   }
 
   public function next()
   {
       $this->_at  ;
       return next($this->_list);
   }
 
   public function valid()
   {
       // Test to see if limit has been reached.
       // Test to see if current exists
       if(($this->_at > 20) or (current($this->_list) == false)) return false;
 
       return true;
   }
 
   public function current()
   {
       return current($this->_list);
   }
 
   public function key()
   {
       return key($this->_list);
   }
}

The execution of the Iterator expected behavior will only work in the foreach loop.

php
foreach(new ArrayPagedList($array) as $current)
{
    print_r($current);
}

Most will use the next method in the while loop. With the default behavior, it will continue on ignoring the limit and page rules.

php
$iterator = new ArrayPagedList($array);
 
while($row = $iterator->next())
{
    // Do stuff.
}

The valid method has to be called to stay within the ruleset of the iterator. This is the wanted behavior for those who wish to bypass the ruleset of the iterator, which can't be done in the foreach loop.

Implementating an Album Iterator

Even without using the SPL Iterator, it is possible for objects to be used in the foreach loop. However, the results aren't what you would expect.

php
class Album
{
 
    public $title;
    public $band;
    public $year;
    private $songs;
 
    public function __construct ($title, $band, $year)
    {
        $this -> title = $title;
        $this -> band = $band;
        $this -> year = $year;
    }
 
    public function song ($title)
    {
        $this -> songs[] = $title;
    }
 
}
 
$ai = new Album ('Forty Licks', 'The Rolling Stones', 2002);
$ai -> song ('Brown Sugar');
$ai -> song ('Street Fighting Man');
$ai -> song ('Rocks Off');
$ai -> song ('Midnight Rambler');
 
foreach ($ai as $key => $value)
{
    print ($key . ' (' . $value . ')');
}

Would display the public variables name and value.

title (Forty Licks)
band (The Rolling Stones)
year (2002)

It isn't desired result to display that for the user. Adding the Iterator Interface gives the expected result.

php
class AlbumIterator implements Iterator
{
 
    public $title;
    public $band;
    public $year;
 
    private $songs;
 
    public function __construct ($title, $band, $year)
    {
        $this -> title = $title;
        $this -> band = $band;
        $this -> year = $year;
    }
 
    public function song ($title)
    {
        $this -> songs[] = $title;
    }
 
    public function current ()
    {
        return current ($this -> songs);
    }
 
    public function key ()
    {
        return key($this -> songs);
    }
 
    public function valid ()
    {
        return current ($this -> songs);
    }
 
    public function rewind ()
    {
        return reset ($this -> songs);
    }
 
    public function next ()
    {
        return next ($this -> songs);
    }
 
}

Now to test using the Iterator.

php
$ai = new AlbumIterator ('Forty Licks', 'The Rolling Stones', 2002);
$ai -> song ('Brown Sugar');
$ai -> song ('Street Fighting Man');
$ai -> song ('Rocks Off');
$ai -> song ('Midnight Rambler');
 
foreach ($ai as $id => $song) {
	print ($song . ' (' . $id . ')<br />');
}

Will display the following.

Brown Sugar (0)
Street Fighting Man (1)
Rocks Off (2)
Midnight Rambler (3)

PHP 4 Example

The Iterator pattern is possible in PHP 4, but can't be used in the foreach loop.

php
class MyIterator
{
    var $data;
 
    function MyIterator($data)
    {
        $this->data = $data;
    }
 
    function rewind()
    {
        return reset($this->data);
    }
 
    function next()
    {
        return next($this->data);
    }
 
    function valid()
    {
        if(!current($this->data))
        {
            return false;
        }
 
        return true;
    }
 
 function current()
    {
        return current($this->data);
    }
 
    function key()
    {
        return key($this->data);
    }
}

You would use the object in a while loop, calling rewind first.

php
$iterator = new MyIterator();
 
$iterator->rewind();
 
while($iterator->valid())
{
    $current = $iterator->current();
    $key = $iterator->key();
 
    $iterator->next();
}


Disadvantages

Iterators don't save you from poor interface methods' implementations and poor optimizations. Using the Iterator Pattern will decrease the speed of the script over the procedural method, but if done right it should be minimal and predictable.

Conclusion

Separation of the iteration and main class functionality adds to the code reuse and speed for which projects can be developed. They are an useful tool for managing data and controlling how a developer has access.

Retrieved from "/wiki/Iterator"
MediaWiki
GNU Free Documentation License 1.2