iterators

Today I’ll look at some iterators: a set of classes in the SPL that implements various iterating patterns: ArrayIterator, AppendIterator, FilterIterator, LimitIterator and NoRewindIterator. Hopefully you’ll get a idea of what these are capable of and that you can get some new ideas for your day-to-day tasks.

ArrayIterator

The ArrayIterator does exactly what it tells you: it’s used to iterate over an array of values, with it you can easily iterate over values like you would any other collection with a normal iterator. It has the ability to seek and rewind on the array in question and get the current key and value.

Let’s start with a simple example that shows the bare bones of what a ArrayIterator looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
// Array of php sites:
$array = array(  
    "php" => "php.net",
    "pecl" => "pecl.php.net",
    "pear" => "pear.php.net",
    "bugs" => "bugs.php.net"
);
 
// ArrayObject wraps a array and gives you OOP
// access to it for manipulation:
$obj = new ArrayObject( $array );
 
// Add a new site to the ArrayObject:
$obj->offsetSet( "cvs", "cvs.php.net" );
 
// How many items are we iterating over?
echo "Iterating over: " . $obj->count() . " values\n";
 
// The foreach construct will call $obj->getIterator() which returns
// an ArrayIterator to travers itself. That ArrayIterator implements 
// interface Iterator and can therefore be used in foreach.
foreach( $obj as $key => $value ) {
	echo "Key=$key, value=$value\n";
}
?>

As you can see: the ArrayIterator is not really used directly, but implicit with the foreach construct. But you don’t have to use the builtin ArrayObject to use the ArrayIterator, in fact: you can add a array directly to the ArrayIterator and iterate over it, but here’s a example that use a custom class that implement the IteratorAggregate interface. The IteratorAggregate interface has a single function to implement getIterator(), this is the “magic” method called in the foreach construct in the previous example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
/**
 * Custom array class
 * It really doesn't do much functionality, all it
 * does is to check that the array elements we
 * are adding doesn't have a key and a value that
 * is the same.
 */
class MyArray implements IteratorAggregate {
	// Collection of items
	private $arr;
 
	/**
	 * Constructor, initialize a empty array   
	 */
 	public function __construct() {
		$this->arr = array();   
	}
 
	/**
	 * Add a new array element to MyArray.
	 * Checks to see if the $key and the $value is
	 * the same before adding it to the internal array.
	 * ( yes: a really bogus example, but this is just for
	 * showing some functionality )
	 */
	public function add( $key, $value ) {
		if( $this->check( $key, $value ) ) {
			$this->arr[$key] = $value;
		}
	}
 
	/**
	 * Private function that checks if $key and $value
	 * are the same.
	 */
	private function check( $key, $value ) {
		if( $key == $value ) {
			return false;
		}
		return true;
	}
 
	/**
	 * The method that a class implementing the
	 * IteratorAggregate interface needs to implement.
	 */
	public function getIterator() {
		return new ArrayIterator( $this->arr );
	}       
}
?>

This class can be used like this:

1
2
3
4
5
6
7
8
9
10
11
$obj = new MyArray();
$obj->add( "php", "php.net" );
// Try to add a element where both key and value
// are the same:
$obj->add( "php", "php" );
 
// Here the getIterator() function will be called on the
// MyArray object
foreach( $obj as $key => $value ) {
	echo "key=$key, value=$value\n";
}

And the output will be ( note that the second element we tried to add isn’t here ):

key=php, value=php.net

So here we’ve looked at how ArrayObject uses the ArrayIterator and how we can create a custom class that can be used to iterate with foreach.

AppendIterator

In the example above we looked at how we can iterate over a single ArrayObject and how the IteratorAggregate interface works, but: if you have multiple elements you might want to iterate over all of these together, this is where the AppendIterator comes to help you.

The following example creates two ArrayObjects and a AppendIterator, there is only a single method here that you need to think about, the append() function that takes a Iterator as argument. The point of the iterator is to collect several collections and iterate over them in one go:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$sites = array( "wiki.cc", "planet-php.net" );
$othersites = array( "slashdot.org", "maclash.org" );
$one = new ArrayObject( $sites );
$two = new ArrayObject( $othersites );
$iterator = new AppendIterator();
$iterator->append( $one->getIterator() );
$iterator->append( $two->getIterator() );
foreach( $iterator as $site ) {
    echo $site . "\n";
}
?>

This will give you:

wiki.cc
planet-php.net
slashdot.org, 
maclash.org 

As you can see: with the AppendIterator you can collect up elements from several sources (objects) and display them all in one go, let’s look at a better example: we have a ‘CMS’ where each type extends a Element class ( all elements in our CMS must be of the same type) , there are also element collections that collects up N-elements and provides us with a Iterator to iterate over all the module elements currently loaded.

To display all links and articles in our system we can come up with the following code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php
abstract class Element {
    public $title;
}
class Link extends Element {
    public function __construct( $title ) {
        $this->title = $title;
    }
}
class Article extends Element {
    public function __construct( $title ) {
        $this->title = $title;
    }
}
abstract class ElementCollection implements IteratorAggregate {
    private $elements;
    public function __construct() {
        $this->elements = new ArrayObject();
    }
    public function addElement( Element $module ) {
    	$this->elements->append( $module );
    }
    public function getIterator() {
        return $this->elements->getIterator();
    }
}
class ArticleCollection extends ElementCollection {
    public function __construct() {
        parent::__construct();
        $this->collectArticles();
    }
    private function collectArticles() {
        // Do some fancy DB stuff
        $a = new Article( "Article one" );
        $b = new Article( "Article two" );
        $this->addElement( $a );
        $this->addElement( $b );
    }
}
class LinkCollection extends ElementCollection {
    public function __construct() {
        parent::__construct();
        $this->collectLinks();
    }
    private function collectLinks() {
        // Do some fancy DB stuff
        $a = new Link( "Link one" );
        $b = new Link( "Link two" );
        $this->addElement( $a );
        $this->addElement( $b );
    }
}
/*
 * Now for the magic: we create two collections and append
 * the iterator for each collection to a single AppendIterator.
 * We can now iterate over all elements in one go and display
 * the module title.
 */
$article = new ArticleCollection();
$link = new LinkCollection();
$it = new AppendIterator();
$it->append( $article->getIterator() );
$it->append( $link->getIterator() );
foreach( $it as $module ) {
    echo $module->title . "\n";
}
?>

This will give you:

Article one
Article two
Link one
Link two

and you have successfully collected elements together to iterate over in one foreach loop.

FilterIterator

There are many ways to iterate over a collection of items, but you might not want to display everything at any given time; say hello to the FilterIterator, a abstract class that have one function you will need to implement: accept.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
/**
 * Our filter, it implements the accept() function,
 * this is where we decide what will be accepted by
 * the foreach further down
 */
class Filter extends FilterIterator {
    /**
     * The filteriterator needs a iterator as param:
     */
	public function __construct( Iterator $it ) {
        parent::__construct( $it );
    }
 
    /**
     *  When we iterate over the array the following
     *  function will be called to see wether we should
     *  accept the current key:
     */ 
    public function accept() {
        if( preg_match( "/^php/", $this->getInnerIterator()->key() ) ) {
			return true;
        }       
        return false;
    }
}
 
$arr = array(  
    "php" => "php.net",
    "php_pecl" => "pecl.php.net",
    "perl" => "perl.org",
    "python" => "python.org"
);
 
$obj = new ArrayObject( $arr ); 
 
$it = new Filter( $obj->getIterator() );
 
foreach( $it as $key => $value ) {
	echo "key=$key, value=$value\n";
}

Running this will give us ( as we would suspect ) the following:

key=php, value=php.net
key=php_pecl, value=pecl.php.net

And we have successfully filtered out anything where the key doesn’t start with php.
A FilterIterator can be very useful when you are iterating over items and want to automaticly check the current item before outputting it ( a “adult filter” can be one example of what you want to implement ).

LimitIterator

Now: you have a large result set, but only want to display a certain part of it, what do you do? Create a counter, increment it and see if we have reached the amount for the current page? No: you create a LimitIterator that will do it for you. The LimitIterator is a class that keeps track of how many items there are in a iterator and display the number you specify in the constructor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
// Array with items:
$arr = array(
    "php" => "php.net",
    "perl"  => "perl.org",
    "python" => "python.org"
);
 
// Array object with all our web sites
$obj = new ArrayObject( $arr ); 
/*
 *  The limit iterator takes a iterator as the first
 *  param, then where you want to start, and finally how
 *  many you want to display:
 */
$it = new LimitIterator( $obj->getIterator(), 0, 1  );
foreach( $it as $key => $value )  {
    echo "key=$key, value=$value\n"; 
}   
?>

We tell the LimitIterator to display from item 0, and show 1 item, running this we will see:

key=php, value=php.net

which is correct.

Let’s create three “pages” where we display one item for each page ( not the best of examples, but it will demonstrate the effect of passing a new starting point to the LimitIterator )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
// Array with items:
$arr = array(
    "php" => "php.net",
    "perl"  => "perl.org",
    "python" => "python.org"
);
 
// Array object with all our web sites
$obj = new ArrayObject( $arr ); 
 
$page = 0;
$perPage = 1;
 
for( $i = 0; $i < 3; $i++ ) {
    echo "\n----Page #" . $page . "-----------\n";
    $it = new LimitIterator( $obj->getIterator(), $page, $perPage  );
    foreach( $it as $key => $value ) {
        echo "key=$key, value=$value\n";
    }   
    $page++;
}
?>

This will give the following output:

----Page #0-----------
key=php, value=php.net

----Page #1-----------
key=perl, value=perl.org

----Page #2-----------
key=python, value=python.org

That’s it for creating a easy LimitIterator over a Iterator, this can easily be implemented to split up large reports into separate files for every N-elements for example, but only you can set the limits on what to use it for.

NoRewindIterator

The NoRewindIterator Iterator is a iterator that doesn’t call rewind(), so it is a Iterator that can only be used once.

First: let’s look at a normal Iterator:

1
2
3
4
5
6
7
8
9
<?php
$sites = array( "wiki.cc", "planet-php.net" );
$arr = new ArrayObject( $sites );
foreach( $arr as $site ) {
    echo $site . "\n";
}
foreach( $arr as $site ) {
    echo $site . "\n";
}?>

This will output:

wiki.cc
planet-php.net
wiki.cc
planet-php.net

But: if we only want the user of a iterator to be able to iterate over a collection once we can use the NoRewindIterator:

1
2
3
4
5
6
7
8
9
10
11
<?php
$sites = array( "wiki.cc", "planet-php.net" );
$arr = new ArrayObject( $sites );
$it = new NoRewindIterator( $arr->getIterator() );
foreach( $it as $site ) {
    echo $site . "\n";
}
foreach( $it as $site ) {
    echo $site . "\n";
}
?>

this will give you:

wiki.cc
planet-php.net

so you can see that we only get the output once, even though we tried to iterate over the collection two times.

The iterators in SPL is a valuable collection that you should have a look at in your day-to-day programming tasks since it has a set of common ways on how to implement various iterating tasks, this will help you and others that will look at your code since they share the commonality of the SPL iterators.

3 comments to “iterators”

You can leave a reply or Trackback this post.
  1. wonderfull article

    I didnt know about this feature and I will start to use it

    Thanks

  2. Maybe you didnot introduce the “IteratorIterator”.

Write a Reply or Comment

Your email address will not be published.