Recently I came across a situation where I need to iterate over multiple result sets. Since SPL has a decent collection of iterators I began my search there. So I came across the MultipleIterator thinking it would solve all my problems. It didn't.
The problem with the MultipleIterator is that it doesn't provide a continuous stream of elements of all the attached iterators, instead it 'groups' them. Per iteration you get an array of all n-th elements for all attached iterators.
Given the following piece of code:
<?php
$iter = new MultipleIterator();
$iter->attachIterator(new ArrayIterator(range(0, 5)));
$iter->attachIterator(new ArrayIterator(range(10, 15)));
foreach ($iter as $row) {
var_dump($row);
}
The output is:
array(2) {
[0]=>
int(0)
[1]=>
int(10)
}
array(2) {
[0]=>
int(1)
[1]=>
int(11)
}
array(2) {
[0]=>
int(2)
[1]=>
int(12)
}
array(2) {
[0]=>
int(3)
[1]=>
int(13)
}
array(2) {
[0]=>
int(4)
[1]=>
int(14)
}
array(2) {
[0]=>
int(5)
[1]=>
int(15)
}
Not exactly what i want. And even worse, all iterators need to be of the same length. It just doesn't cover my use case where I simply want to group a bunch of iterators and interface with them as if it were a single one.
It seems the SPL doesn't provide a solution to my use case, so I wrote my own. This is the implementation I ended up with:
<?php
class MultiIterator
implements Iterator
{
/**
* Holds the stack of iterators
* @var array
*/
protected $iterators = array();
/**
* Holds the index to the current interator
* @var integer
*/
protected $current_iterator_idx;
/**
* Add a new iterator to the stack
* @param Iterator $iterator
*/
public function appendIterator(Iterator $iterator)
{
$this->iterators[] = $iterator;
}
/**
* Returns the currently used iterator
* @return Iterator
*/
public function iterator()
{
return (isset($this->iterators[$this->current_iterator_idx])) ? $this->iterators[$this->current_iterator_idx] : false;
}
/**
* (non-PHPdoc)
* @see Iterator::current()
*/
public function current()
{
return $this->iterator()->current();
}
/**
* (non-PHPdoc)
* @see Iterator::next()
*/
public function next()
{
//does nothing
return $this->iterator()->next();
}
/**
* (non-PHPdoc)
* @see Iterator::key()
*/
public function key()
{
return $this->iterator()->key();
}
/**
* (non-PHPdoc)
* @see Iterator::valid()
*/
public function valid()
{
if (!count($this->iterators)) {
return false;
}
//check if current iterator is valid
$valid = $this->iterator()->valid();
if (false === $valid) { //this iterator has dried up, try and find a new one
//increment the index to find a new iterator
$this->current_iterator_idx++;
//now check if we have a new iterator
if (false !== $this->iterator()) {
//we have a new iterator, valid or not
return $this->iterator()->valid();
} else {
//no more iterators
return false;
}
}
//apparantly, the current iterator is still valid
return $valid;
}
/**
* (non-PHPdoc)
* @see Iterator::rewind()
*/
public function rewind()
{
//reset state
$this->current_iterator_idx = 0;
array_walk($this->iterators, function($iter) {
$iter->rewind();
});
}
}
With the expected usage and output:
<?php
$iter = new MultiIterator();
$iter->appendIterator(new ArrayIterator(range(0, 6)));
$iter->appendIterator(new ArrayIterator(range(10, 15)));
foreach ($iter as $row) {
var_dump($row);
}
int(0)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(10)
int(11)
int(12)
int(13)
int(14)
int(15)
Problem solved.