Working with iterators and arrays SPL

Iterators are the next thing from the SPL I am going to take a look at. For those that are not familiar with iterators yet, an iterator is like a list or a collection of items that you can move through (traverse). If that sounds familiar to you, it should. They share a lot of features and functionality that an array has in PHP. The biggest differences are that they are often more efficient with memory, faster to process, and object oriented.

 Iterators

There are a lot of iterators in PHP. Not all of them are part of the SPL though, such as SimpleXMLIterator, so keep that in mind as you may come across an iterator in places you may not expect. It can be pretty intimidating looking at the list, and it only gets worse when you see something called a RecursiveIteratorIterator.

ArrayIterator

ArrayIterator is probably going to be the best spot to start out. ArrayIterator bridges the gap between Array and Iterator and allows for conversion back and forth. In it’s most basic implementation it accepts an array as the parameter and it converts it into an iterator.

$cities = array('Torrington', 'Burlington', 'New York City', 'Warwick');
$cities = new ArrayIterator($cities);

/*
You can alternatively use these methods to add items to an ArrayIterator
$cities = new ArrayIterator();
$cities->append('Torrington');
$cities[] = 'Burlington';
$cities[] = 'New York City';
$cities->append('Warwick');
*/

The first thing you will realize is that the ArrayIterator has methods for some of the more common array functions such as asort, ksort, natsort, key, etc.. The second thing you will realize is that iterators do not have a 1:1 conversion in terms of array functions to iterator methods. So it may not be a suitable replacement for all instances where an array is currently used.

Chain Iterators Together

One great thing about iterators is the ability to chain them together. It opens up some pretty powerful capabilities such as filtering, merging items, and limiting output.  Some of it like limiting results, is exclusive to iterators, but other things like filtering can also be done using the array_ functions.

$cities = array('Torrington', 'Burlington', 'New York City', 'Warwick');
$cities = new ArrayIterator($cities);
$cities = new LimitIterator($cities, 0, 2);
foreach ($cities as $city) {
    echo $city;
}
// returns Torrington, Burlington

FilterIterator

FilterIterator is an abstract class that you can use to create custom filters to apply to the data in an iterator.

class CityFilter extends FilterIterator {
    protected $City;

    public function __construct(Iterator $iterator, $city) {
        parent::__construct($iterator);
        $this->City = $city;
    }

    public function accept() {
        return $this->current() == $this->City;
    }
}

$cities = array('Torrington', 'Burlington', 'New York City', 'Warwick');
$cities = new ArrayIterator($cities);
$cities = new CityFilter($cities, 'Burlington');

foreach ($cities as $city) {
    echo $city;
}
// returns Burlington

Merging Iterators

I mentioned earlier that you can merge iterators together. You do that with AppendIterator.

$cities1 = array('Torrington', 'Burlington');
$cities1 = new ArrayIterator($cities1);

$cities2 = array('New York City', 'Warwick');
$cities2 = new ArrayIterator($cities2);

$cities = new AppendIterator();
$cities->append($cities1);
$cities->append($cities2);

foreach ($cities as $city) {
    echo $city;
}
// returns Torrington, Burlington, New York City, Warwick

Converting an Iterator to an Array

So I showed you how to go from an array to an iterator. But what if you want to take your iterator and go back to an array if you need to use a function that is not available as a method? You have two options.

Option 1: ArrayIterator::getArrayCopy()

$cities = array('Torrington', 'Burlington', 'New York City', 'Warwick');
$cities = new ArrayIterator($cities);
$cities = $cities->getArrayCopy();

Option 2: iterator_to_array() function

$cities = array('Torrington', 'Burlington', 'New York City', 'Warwick');
$cities = new ArrayIterator($cities);
$cities = iterator_to_array($cities);

One difference between the two options is that ArrayIterator::getArrayCopy() will not execute chained iterators while iterator_to_array() will.

$cities = array('Torrington', 'Burlington', 'New York City', 'Warwick');
$cities = new ArrayIterator($cities);
$cities = new LimitIterator($cities, 0, 2);

$cities1 = iterator_to_array($cities);
print_r($cities1);
// returns Array ( [0] => Torrington [1] => Burlington )

$cities2 = $cities->getArrayCopy();
print_r($cities2);
// returns Array ( [0] => Torrington [1] => Burlington [2] => New York City [3] => Warwick )

 Closing

There are a lot more iterators I didn’t go over for you to explore, like the EmptyIterator in all its glory! I’m noticing one of the biggest advantages over array_ functions is the ease of reading the code. I can’t tell you how many times i’ve had to look at something that uses array_walk and array_map in the same variable and had to spend a few minutes working my way through what it does.

$something = array_walk(array_map(function(){
// some logic
}, $data) ,function() {
// some logic
});

An alternative to that might be to do:

$something = ArrayIterator($data);
$something = CallbackFilterIterator($something, function($current, $key, $iterator) {
// some logic
});

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>