Another common PHP SPL Iterators we will talk about in this part which are callback filter iterator, caching iterator, and recursive array iterator.
Series Topics:
- PHP Spl Iterators Part 1: Traversable, Iterable, Iterator, and Iterator Aggregate
- PHP Spl Iterators Part 2: Array, and Filesystem Iterators
- PHP Spl Iterators Part 3: Filter, Append and Multiple Iterators
- PHP Spl Iterators Part 4: Infinite, Glob, Limit and noRewind Iterators
- PHP Spl Iterators Part 5: Callback Filter, Caching and Recursive array Iterators
Callback Filter Iterator
The callback filter iterator the same as the filter iterator we talked about previously except that in this time the iterator filter according to a callback function passed as a second argument to the constructor.
CallbackFilterIterator extends FilterIterator implements OuterIterator { /* Some methods */ public __construct ( Iterator $iterator , callable $callback ) public bool accept ( void ) /* Some inherited methods */ public abstract bool FilterIterator::accept ( void ) public FilterIterator::__construct ( Iterator $iterator ) public mixed FilterIterator::current ( void ) .... }
As you see that the iterator constructor takes a callback to do the actual job, someone could ask what is the functionality of accept() here, the answer is that accept() just calls the callback passed to the constructor
look at the difference between FilterIterator class structure:
abstract FilterIterator extends IteratorIterator implements OuterIterator { /* Some iterator methods */ public abstract bool accept ( void ) public __construct ( Iterator $iterator ) }
The Filter Iterator is declared abstract so to use it you have to extend it while the Callback Filter Iterator doesn’t need to be extend just create an instance and pass the callback and that’s it.
The Callback function accept three arguments:
- the current item
- the current key
- the iterator
Let’s see this example:
<?php $products = array( array( 'name' => 'Pants', 'available' => 1, 'sold' => 1, ), array( 'name' => 'Dresses', 'available' => 0, 'sold' => 0, ), array( 'name' => 'Jackets', 'available' => 1, 'sold' => 1, ), array( 'name' => 'Shoes', 'available' => 1, 'sold' => 0, ), ); function get_sold_items($current) { return $current['sold'] == 1; } $soldItems = new CallbackFilterIterator(new ArrayIterator($products), 'get_sold_items'); echo "<pre>"; echo "<h2>Displaying sold items</h2>"; foreach ($soldItems as $item) { echo "Item name: " . $item["name"] . "\n"; } // output // Displaying sold items // Item name: Pants // Item name: Jackets ?>
As shown in the code above we created an instance of the CallbackFilterIterator passing the iterator object (which can any iterator class) in this case ArrayIterator, and the second argument the callback which in this case the function name as string.
We can also pass the callback as anonymous function like so:
$soldItems = new CallbackFilterIterator(new ArrayIterator($products), function($current) { return $current['sold'] == 1; });
Another example to return the available items:
<?php $availableItems = new CallbackFilterIterator(new ArrayIterator($products), function($current) { return $current['available'] == 1; }); echo "<h2>Displaying available items</h2>"; foreach ($availableItems as $item) { echo "Item name: " . $item["name"] . "\n"; }
Using the directory iterator along with the callback filter iterator to filter the filesystem:
<?php $dirIterator = new DirectoryIterator(__DIR__); $filesOnly = new CallbackFilterIterator($dirIterator, function ($current, $key, $iterator) { return $current->isFile(); }); echo "<h2>Displaying the files only</h2>"; foreach ($filesOnly as $file) { echo "File name: " . $file->getFilename() . "<br/>"; } $dirIterator = new DirectoryIterator(__DIR__); $dirsOnly = new CallbackFilterIterator($dirIterator, function ($current, $key, $iterator) { return $current->isDir() && !$iterator->isDot(); }); echo "<h2>Displaying the directories only</h2>"; foreach ($dirsOnly as $file) { echo "File name: " . $file->getFilename() . "<br/>"; }
So as you see the Callback Filter Iterator makes it easy to filter collections without extending the class, just create a new instance and implement the callback function.
Caching Iterator
The caching iterator offers a mechanism to support a cached iteration over another iterator. After that using a special method getCache() we can retrieve the contents of the cache.
CachingIterator extends IteratorIterator implements OuterIterator , ArrayAccess , Countable { /* Some constants */ const integer CALL_TOSTRING = 1 ; const integer CATCH_GET_CHILD = 16 ; const integer FULL_CACHE = 256 ; /* Some methods */ public __construct ( Iterator $iterator [, int $flags = self::CALL_TOSTRING ] ) public int count ( void ) public void current ( void ) public array getCache ( void ) ... }
Caching Iterator Constants:
CALL_TOSTRING
: Convert every element to stringCATCH_GET_CHILD
: When accessing a children don’t throw exceptionTOSTRING_USE_KEY
: Use CachingIterator::key() for conversion to string.TOSTRING_USE_CURRENT
: use CachingIterator::current() for conversion to string.TOSTRING_USE_INNER
: use CachingIterator::getInnerIterator() for conversion to string.FULL_CACHE
: Cache all readable data.
One important note when using the Caching Iterator is that the caching done during iteration on the data which means if you create the caching iterator instance and then call getCache() directly it will be empty as shown in this example:
<?php $data = range(1, 1000); // add into cache $cacheIterator = new CachingIterator(new ArrayIterator($data), CachingIterator::FULL_CACHE); // get all cached items echo "<pre>"; print_r($cacheIterator->getCache()); // will output empty array echo "</pre>";
So to retrieve the cached data correctly you have to loop over the data first in order for the iteration to be saved in cache
<?php $data = range(1, 1000); // add into cache $cacheIterator = new CachingIterator(new ArrayIterator($data), CachingIterator::FULL_CACHE); // the loop here is important foreach ($cacheIterator as $c) { echo $c; } // get all cached items echo "<pre>"; print_r($cacheIterator->getCache()); echo "</pre>";
When adding items into the Cached Iterator it’s important to use the flagCachingIterator::FULL_CACHE
otherwise you encounter this exception:
Fatal error: Uncaught BadMethodCallException: CachingIterator does not use a full cache (see CachingIterator::__construct)
Recursive Array Iterator
This iterator is the same as array iterator as it allows to unset and modify values and keys while iterating over Arrays and Objects, in addition to that it can iterate over the current iterator entry.
RecursiveArrayIterator extends ArrayIterator implements RecursiveIterator { /* Some inherited constants */ const integer STD_PROP_LIST = 1 ; const integer ARRAY_AS_PROPS = 2 ; /* Some constants */ const integer CHILD_ARRAYS_ONLY = 4 ; /* Some methods */ public RecursiveArrayIterator getChildren ( void ) public bool hasChildren ( void ) public void ArrayIterator::append ( mixed $value ) public void ArrayIterator::asort ( void ) ... }
The recursive array iterator is most powerful when dealing with multidimensional arrays or arrays which has inconsistent structure like arrays that contact other singular and child arrays.
A real world example to demonstrate this iterator, consider we have a navigation menu that represent website navigation like this:
<?php $navMenu = array( 0 => "Home", "services" => array("multimedia", "web applications" => array(0 => "web design", 1 => "web development", 2 => array("frontend", "backend"))), 2 => "About us", "careers" => array("web developer", "frontend developer", "designer"), 3 => "Contact us" );
Now let’s pass this array to the Recursive array iterator like so:
$iterator = new RecursiveArrayIterator($navMenu); echo "<pre>"; print_r($iterator); echo "</pre>";
If you run the above code you will see that the iterator displays the items in hierarchy like this:
RecursiveArrayIterator Object ( [storage:ArrayIterator:private] => Array ( [0] => Home [services] => Array ( [0] => multimedia [web applications] => Array ( [0] => web design [1] => web development [2] => Array ( [0] => frontend [1] => backend ) ) ) [2] => About us [careers] => Array ( [0] => web developer [1] => frontend developer [2] => designer ) [3] => Contact us ) )
Now to traverse all the items we can create a function that will be called recursively passing the iterator object like this one:
function traverseIterator($iterator) { while ( $iterator->valid() ) { if ( $iterator->hasChildren() ) { echo "<strong>" . $iterator->key() . "</strong><br/>"; traverseIterator($iterator->getChildren()); } else { echo $iterator->current() ."<br/>"; } $iterator->next(); } } echo "<pre>"; traverseIterator($iterator); echo "</pre>"; // output Home services multimedia web applications web design web development frontend backend About us careers web developer frontend developer designer Contact us
In the above function all we iterated over the iterator using a while loop and then checked if the iterator has children using RecursiveArrayIterator::hasChildren(), if it has we called the function again passing the child iterator (RecursiveArrayIterator::getChildren()), otherwise we displayed the items (key and value).
Conclusion
We talked in this tutorial of our series (PHP Iterators) about three more SPL Iterators which are CallbeckFilterIterator for filter arrays using a callback, CachingIterator for caching iterations, and RecursiveArrayIterator for manipulating collections in a recursive manner.