Engine Yard Blog RSS Feed

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";
}
  1. Internally PHP first calls `rewind()` to reset the pointer of the iterator. In our case, since we have an `OuterIterator`, that is called out the `OuterIterator` and passed down to the main `Iterator`.
  2. `accept()` is called on the first element in the inner `Iterator`. If false is returned, the inner `Iterator`'s `next()` is called to move the pointer and the outer iterator's `accept()` method is called again. This continue until the end of the inner iterator is reached, or `accept()` returns `true`.
  3. Once `accept()` returns true, the `OuterIterator`'s `valid()` is called to ensure we are looking at a valid element.
  4. When `valid()` returns true,  the `OuterIterator`'s `current()` is called to return the value of the current element.
  5. 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);
    }

    public function accept()
    {
        echo "-- Accept\n";
        if(strpos($this->getInnerIterator()->current(),$this->test)!==false) {
            echo "   Accepting " . $this->getInnerIterator()->current() . "\n";
            return true;
        } else {
            echo "   Ignoring " . $this->getInnerIterator()->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 the `foreach` 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 an `OuterInterface`, it was passed through to the inner `Iterator`.
  • Our `accept()` method was called. Notice that since "Bashful " does not contain a "y", we ignore it. Internally, meaning at the Zend Engine level, the `next()` method on the inner `Iterator` was called. We don't see it here because we are only tracking calls on methods of our `OuterIterator`.
  • `accept()` is called again to check the new current value in the inner `Iterator`. "Sneezy " contains a "y" so we accept it.
  • `current()` is called to populate `$dwarf` in the `foreach`.
  • `key()` is called to populate `$key` in the `foreach`.
  • "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.

fClose()

OuterIterator is a powerful tool, and yet relatively unknown tool. If you search Github for OuterIterator you will come up with 1,106 PHP files that have one. (as opposed to 644,917 files that contain the command "eval". That statistic alone should make any professional developer cry.)

Once you understand the OuterIterator you will find places in your code to use it, or one of it's extenders. The more you know about Iterators and the SPL in general, the faster you will be able to create your projects.


Tagged:

comments powered by Disqus