The Outer Iterator
Note: Here’s another great post from the community, specifically from Cal Evans! Many moons ago, at the tender age of 14, Cal touched his first computer. (We’re using the term “computer” loosely here, it was a TRS-80 Model 1) Since then his life has never been the same. He graduated from TRS-80s to Commodores and eventually to IBM PC’s. For the past 13 years Cal has worked with PHP and MySQL on Linux, OSX, and Windows. He has built a variety of projects ranging in size from simple web pages to multi-million dollar web applications. When not banging his head on his monitor, attempting a blood sacrifice to get a particular piece of code working, he enjoys writing books and sharing what he has discovered. His most recent being Signaling PHP.
When PHP 5 arrived there was great excitement and rejoicing over the new object model. For many of us who struggled through PHP 4’s “Object Based” paradigm, PHP 5 was a ray of sunshine. We were so excited that many of us lost sight of another important addition to PHP 5, the Standard PHP Library (SPL).
Since then, the excitement over the object model has died down. Many authors and speakers have talked about, blogged about, and written about the SPL. However the SPL itself is a very large topic. We are going to narrow our focus down a bit to a subtopic of a specific section of the SPL. This blog post will deal with the OuterIterator
. This is an interface defined in the SPL and used by several of the built in iterators.
What is an Outer Iterator?
Iterators, as the name implies allow you to iterate over a collection. In many cases that collection is an array. However, PHP allows you to iterate over many other types of collections. A directory structure, XML, even database cursors can all be easily iterated over using the built in iterator classes. There are times however, when what you need to iterate over…is another Iterator
that is itself iterating over something. For this very instance, the SPL has defined an interface for us the OuterIterator
.
The OuterIterator
interface extends the Iterator
interface but adds a single method, getInnerIterator()
.
Why use it
The easiest way to understand the OuterIterator
is by looking at an example. The easiest OuterIterator
example to understand is the FilterIterator
. FilterIterator
isn’t a concrete iterator, it is a abstract class that implements OuterIterator
. If you understand it then you understand the basics of the OuterIterator
and begin to understand why you would use it.
The FilterIterator
is exactly what you think it should be from it’s name. It is a way to intercept the process of iterating and refuse to return the current element based on your logic.
FilterIterator
is an abstract class that you extend to create your own class. Because it extends the IteratorIterator
class, you do not have to flesh out the five main methods of an iterator yourself. With FilterIterator
you only have to write code for the method, accept()
.
accept()
returns a boolean that decides whether the current element of the inner iterator is displayed or skipped. Execute whatever code you wish inside of accept()
but if it returns false
, the element will be skipped and the inner iterator will have it’s pointer moved to the next element and the process repeated. This will continue until either the end of the inner Iterator
is reached or accept()
returns true.
Internally, FilterIterator
uses the one method of the OuterIterator
, getInnerIterator()
, to process the records of the inner iterator as the methods of FilterIterator
are called.
How an OuterIterator
operates
To understand the process, we need to think about how the FilterIterator
works internally. Normally, if you drop an iterable item into a foreach
loop, the loop does this:
foreach($myThing as $thisThing) {
echo $thisThing . "\n";
}
- Internally PHP first calls
rewind()
to reset the pointer of the iterator. In our case, since we have anOuterIterator
, that is called out theOuterIterator
and passed down to the mainIterator
. accept()
is called on the first element in the innerIterator
. If false is returned, the innerIterator
’snext()
is called to move the pointer and the outer iterator’saccept()
method is called again. This continue until the end of the inner iterator is reached, oraccept()
returnstrue
.- Once
accept()
returns true, theOuterIterator
’svalid()
is called to ensure we are looking at a valid element. - When
valid()
returns true, theOuterIterator
’scurrent()
is called to return the value of the current element. - The the code inside the
foreach
is executed.
We could have also written our foreach
to return the current key as well.
foreach($myThing as $key=>$thisThing) {
echo $thisThing . "\n";
}
This would have added a 6th step to our list above where key()
would have been called.
So now we know that accept()
is called early on in the process and will continue to be called until
either accept()
returns true
or the end of the inner iterator is reached.
To demonstrate what we have just learned, let’s build a quick FilterIterator that will iterate through the Seven Dwarves.
$dwarves = ['Grumpy ','Happy ','Sleepy ', 'Bashful ', 'Sneezy ', 'Dopey ', 'Doc '];
$myDwarfPrinter = new DwarfPrinter(new ArrayIterator($dwarves),"y");
echo "Begin Loop\n";
foreach($myDwarfPrinter as $key=>$dwarf) {
echo "++ " . $key . " - " .$dwarf ."\n";
}
die();
class DwarfPrinter extends FilterIterator
{
protected $test = ' ';
public function __construct($iterator,$test=' ') {
echo "-- Constructor\n";
$this->test = $test;
return parent::__construct($iterator)<wbr></wbr>;
}
public function accept()
{
echo "-- Accept\n";
if(strpos($this-><wbr></wbr>getInnerIterator()->current(),<wbr></wbr>$this->test)!==false) {
echo " Accepting " . $this->getInnerIterator()-><wbr></wbr>current() . "\n";
return true;
} else {
echo " Ignoring " . $this->getInnerIterator()-><wbr></wbr>current() . "\n";
return false;
}
}
public function next()
{
echo "-- Next\n";
return parent::next();
}
public function current()
{
echo "-- Current\n";
return parent::current();
}
public function key()
{
echo "-- Key\n";
return parent::key();
}
public function rewind()
{
echo "-- Rewind\n";
return parent::rewind();
}
public function valid()
{
echo "-- Valid\n";
return parent::valid();
}
}
You can copy that code, paste it into a file, save it as test.php
(oh don’t look at me like you don’t have a hundred test.php
files scattered around your file system) and then run it with
$ php test.php
This is the line that actually instantiates the OuterIterator
class we created.
$myDwarfPrinter = new DwarfPrinter(new ArrayIterator($dwarves),"y");
The second parameter, "y"` determines which of the dwarf names get printed. If you run it as-is, every dwarf name containing a 'y' will be printed.
- Grumpy
- Happy
- Sleepy
- Sneezy
- Dopey
More importantly though, you see when each method is called in the process. If you want to print an unfiltered list, use " "
as the second parameter. All of the names in the $dwarves
array end in a space, so filtering for the name containing a space will print each of them.
Let’s look at a snippet of the output:
++ 2 - Sleepy
-- Next
-- Accept
Ignoring Bashful
-- Accept
Accepting Sneezy
-- Valid
-- Current
-- Key
++ 4 - Sneezy
- Any line starting with
++
was output as part of theforeach
loop. Any line starting with--
is a marker indicating that a method was called. - The first line tells us that the second element, “Sleepy” was output.
- The
next()
method was called to move the pointer. Because this was called on our implementation of anOuterInterface
, it was passed through to the innerIterator
. - Our
accept()
method was called. Notice that since “Bashful “ does not contain a “y”, we ignore it. Internally, meaning at the Zend Engine level, thenext()
method on the innerIterator
was called. We don’t see it here because we are only tracking calls on methods of ourOuterIterator
. accept()
is called again to check the new current value in the innerIterator
. “Sneezy “ contains a “y” so we accept it.current()
is called to populate$dwarf
in theforeach
.key()
is called to populate$key
in theforeach
.- “Sneezy” is output.
Now you see exactly how an OuterIterator
is used. You also have a good understanding of how iterators work under the hood.
When do you use it?
OuterIterators
are used anytime you need to manipulate an Iterator
. It is a wrapper that adds functionality to an existing Iterator
The following is a list of the concrete iterators built into PHP via the SPL that implement the OuterIterator
. As you can see the Core Developers have given us many tools to work with and covered a lot of the situations where you would normally write the code to implement.
AppendIterator
CachingIterator
CallbackFilterIterator
FilterIterator
InfiniteIterator
IteratorIterator
LimitIterator
ParentIterator
RecursiveCachingIterator
RecursiveCallbackFilterIterator
RecursiveFilterIterator
RecursiveIteratorIterator
RecursiveTreeIterator
RegexIterator
Share your thoughts with @engineyard on Twitter