Why you should use PHP Iterators and IteratorFilters

posted on: 2010-07-04 10:29:49




One of the major advantages to come from the SPL is the introduction on the iterator interface and her derived classes.
The iterator interface contains 6 abstract functions.


 
	abstract public mixed current ( void )
	abstract public scalar key ( void )
	abstract public void next ( void )
	abstract public void rewind ( void )
	abstract public boolean valid ( void )
	

These functions are invoked when your iterator is passed to a foreach.
One of the questions that developers ask is why do we need to use them when using an array and a foreach works just fine?.... After all why would anyone want to over complicate their code by implementing iterator or inheriting one of the many iterator classes when you just want to loop through an array.
The answer is simple. Using them will give you a structured approach to applying logical operations to your code.
For an example consider the following code.

 
class Item {
	var $a;
	function __construct($int) { $this->a = $int; }
}
 
$kvp = array();
 
for($i=0;$i<10;$i++) { $kvp[$i] =  new Item($i); }
 
foreach($kvp as $k => $v) {
	if($v->a != 4 ) {
		echo $v->a;
	}
}

The same can be achieved using the following SPL code.

 
class NoFourFilterIterator extends FilterIterator {
    public function accept() {
	$obj = parent::current();
        return ($obj->a != 4);
    }
}
 
$arrayIterator = new ArrayIterator($kvp);
$noFoursFilter = new NoFourFilterIterator($arrayIterator);
 
foreach ($noFoursFilter as $v) {
	echo $v->a;
}

You might be asking yourself, why would you possibly want to do that. The later example uses more lines of code, extends a class and creates 2 new instances just to achieve the same result.
The point to take on board, is where the logic now resides. By using the FilterIterator you can remove filter logic from the loop and manage it exclusive from the internals of the foreach loop. This opens the door to many possibilities. We could could now define a several FilterIterators and pass our arrayiterator through which ever filters we choose or we could make an all powerful super filter class to manage all logic. The possibilities boggle the mind :) .
So now let's consider the another SPL iterator, the RecursiveIteratorIterator. The benefits of using this iterator are clear. It works likes a standard iterator but as the name states it traverses it's child arrays. Below I also inherit the FilterIterator in a slightly different way. As you will see I override the constructor to take values I will use for my filter operation. Again here there is the possibility use arrays or objects instead of the simple values I pass to the constructor for the key and value. The point here is that I am defining what is to be filtered from outside the filter class. If you were using a FilterIterator for filtering price or brand for example, you could use the same class and pass in different values depending on what preferences a user has selected.

 
class CustomFilterIterator extends FilterIterator {
	var $filter_key;
	var $filter_value;
	public function __construct($iter, $key, $value) {
		parent::__construct($iter);
		$this->filter_key = $key;
		$this->filter_value = $value;
	}
	public function accept() {
		$obj = parent::current();
		return ($obj[$this->filter_key] == $this->filter_value);
	}
}
 
$kvp = array(
	array('brand'=>'1', 'price'=>'4', 'c'=>'7'),
	array('brand'=>'2', 'price'=>'5', 'c'=>'8'),
	array('brand'=>'3','price'=>'6', 'type'=>'9')
);
 
$iterator = new CustomFilterIterator (new RecursiveIteratorIterator(new RecursiveArrayIterator($kvp)), "brand",  "2" );
foreach ($iterator as $k => $v) {
	echo $k."=>".$v." ";
}
 

The RecursiveIteratorIterator also neatly complies to the the composite design pattern. And one feature I personally like is the function ::getDepth(). This function allows you to get the current depth at any point of your recursion. If you are dealing with a multi-dimensional arrays and you won't know until runtime how deep they will go, getDepth will always return the current level (0 for root). This would be very handy if you were building a menu structure for navigation, you could make a custom iterator with separate functions for rendering different levels.
To conclude, I think that you should use Iterator at your own discretion. For looping through a simple array you choose a simpler approach, but when you begin to find yourself applying more conditional logic you should consider an IteratorFilter and when you need to manipulate the mechanism of how you loop through your arrays you should consider extending an SPL iterator class. I hope that tutorial helps provide some insight as to why iterators are useful.