You already iterated over a collection or array in php before using something like for loop or while loop or foreach but you haven’t heard about most advanced topic which is iterators.
Topics we will cover in this series:
- 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
PHP iterators is one of the advanced features that enables you to iterate over a collection like arrays but what makes it so powerful is the ability the control the iteration sequence and control the loop to move backward and forward with flexible way than the ordinary loop. PHP enables you to create a custom iterator class by implementing the iterator interface and also provides some ready to use iterators like the SPL iterators which we will demonstrate them soon in future tutorials.
Traversables:
As you see in the above diagram the all iterator classes and interfaces implement the traversable interface, there are two important interfaces iterator and iteratorAggregate which we will explore them shortly in this tutorial. If you want to create a custom iterator you must implement either iterator or IteratorAggregate. First let’s describe the Traversable interface.
The Traversable interface used to detect if a class is traversable using foreach. It can not be used alone. Instead it must be implemented by either IteratorAggregate or Iterator
Traversable { }
This interface has no methods, its only purpose is to be the base interface for all traversable classes.
One common usage of this interface is to check if an item is instance of traversable like this:
<?php if( !is_array( $items ) && !$items instanceof Traversable ) //Throw exception here ?>
Iterable:
The iterable pseudo-type introduced in PHP 7.1. It accepts any array or object implementing the Traversable interface. Both of these types are iterable using foreach.
Iterable can be used as a parameter type to indicate that a function requires a set of values, but does not care about the form of the value set since it will be used with foreach
<?php function foo(iterable $iterable) { foreach ($iterable as $value) { // ... } } ?>
As shown in the above code we used iterable to restrict the function from accepting any arguments that are not iterable.
To check for some item that are iterable or not we can rewrite the previous example using the is_iterable() function like so:
<?php if ( !is_iterable( $items )) //Throw exception here ?>
since is_iterable() will match both traversable and array the above code is equivalent to the previous example using Traversable interface.
Parameters declared as iterable may use NULL
or an array as a default value.
<?php function foo(iterable $iterable = []) { // ... } ?>
Iterable can also be used as a return type to indicate a function will return an iterable value. If the returned value is not an array or instance of Traversable, a TypeError will be thrown.
<?php function bar(): iterable { return [1, 2, 3]; } ?>
The Iterator Interface:
Interface for external iterators or objects that can be iterated themselves internally. This interface used in what’s called the (Iterator Pattern). This interface extends from the Traversable interface and contain the following methods that need to be implemented:
Iterator extends Traversable { /* Methods */ abstract public mixed current ( void ) // Return the current element abstract public scalar key ( void ) // Return the key of the current element abstract public void next ( void ) // Move forward to next element abstract public void rewind ( void ) // Rewind the Iterator to the first element abstract public bool valid ( void ) // Checks if current position is valid }
This interface come in handy when you have an array and you want to convert it to iterator like the following example:
<?php class hillstations implements Iterator { private $places = []; private $count = 0; private $index = 0; public function current() { return $this->places[$this->index]; } public function next() { $this->index++; } public function rewind() { $this->index = 0; } public function key() { return $this->index; } public function valid() { return isset($this->places[$this->key()]); } public function reverse() { $this->places = array_reverse($this->places); $this->rewind(); } public function addPlace(string $place) { array_push($this->places, $place); $this->count++; } public function removePlace($place) { $index = array_search($place, $this->places); if(isset($this->places[$index])) { unset($this->places[$index]); $this->count--; } } public function totalCount() { return $this->count; } } $hillstation = new hillstations(); $hillstation->addPlace("USA"); $hillstation->addPlace("London"); $hillstation->addPlace("Berlin"); $hillstation->addPlace("Paris"); $hillstation->addPlace("Russia"); $hillstation->addPlace("Morroco"); foreach ($hillstation as $key => $value) { echo "Key: ".$key."----"; echo "Value: ".$value."<br/>"; } $hillstation->reverse(); echo "<hr/>"; foreach ($hillstation as $key => $value) { echo "Key: ".$key."----"; echo "Value: ".$value."<br/>"; }
As you see in the above code we implemented the iterator interface and add implementations for the five methods shown above. Iterator needs an array to work with so we added a function that push a new item into the places array like this:
$hillstation->addPlace("USA");
Then we looped over the $hillstation object as if it where an array displaying the key and the value because as we mentioned previously that iterators can be looped like arrays.
So why we override the five functions in the above class and what are the benefits of them. The answer is if you think about the for loop or foreach loop, in order to iterate over an array it needs to now the start element, current element, the current key, the next element, is the current element is valid. This is the same concept in iterators so you have to implement above mentioned functions.
To illustrate that this working the same as arrays let’s add a print state inside each function like this:
<?php set_time_limit(0); class hillstations implements Iterator { private $places = []; private $count = 0; private $index = 0; public function current() { echo "Current called---"; return $this->places[$this->index]; } public function next() { echo "Next Called---"; $this->index++; } public function rewind() { echo "Rewind called---"; $this->index = 0; } public function key() { echo "Key Called---"; return $this->index; } public function valid() { echo "Valid called---"; return isset($this->places[$this->key()]); } public function reverse() { $this->places = array_reverse($this->places); $this->rewind(); } public function addPlace(string $place) { array_push($this->places, $place); $this->count++; } public function removePlace($place) { $index = array_search($place, $this->places); if(isset($this->places[$index])) { unset($this->places[$index]); $this->count--; } } public function totalCount() { return $this->count; } } $hillstation = new hillstations(); $hillstation->addPlace("Sohag"); $hillstation->addPlace("Assuit"); $hillstation->addPlace("El-minia"); $hillstation->addPlace("Beni suef"); $hillstation->addPlace("Giza"); $hillstation->addPlace("Cairo"); foreach ($hillstation as $key => $value) { echo "Key: ".$key."----"; echo "Value: ".$value."<br/>"; } $hillstation->reverse(); echo "<hr/>"; foreach ($hillstation as $key => $value) { echo "Key: ".$key."----"; echo "Value: ".$value."<br/>"; }
When you run the above code you notice the following:
- Before the first iteration of the loop, Iterator::rewind() is called.
- Before each iteration of the loop, Iterator::valid() is called.
- It Iterator::valid() returns false, the loop is terminated.
- If Iterator::valid() returns true, Iterator::current() and Iterator::key() are called.
- The loop body is evaluated.
- After each iteration of the loop, Iterator::next() is called and we repeat from step 2 above.
The loop isn't terminated until Iterator::valid() returns false or the body of the loop executes a break statement.
The only two methods that are always executed are Iterator::rewind() and Iterator::valid() (unless rewind throws an exception).
The
Iterator::next() method need not return anything. It is defined as
returning void. On the other hand, sometimes it is convenient for this
method to return something, in which case you can do so if you want.
If your iterator is doing something expensive,
like making a database query and iterating over the result set, the best
place to make the query is probably in the Iterator::rewind()
implementation.
Let’s see another example implementation of the Iterator interface for arrays which works with maps (key / value) pairs as well as traditional arrays:
class tIterator_array implements Iterator { private $myArray; public function __construct( $givenArray ) { $this->myArray = $givenArray; } function rewind() { return reset($this->myArray); } function current() { return current($this->myArray); } function key() { return key($this->myArray); } function next() { return next($this->myArray); } function valid() { return key($this->myArray) !== null; } } ?>
The IteratorAggregate Interface:
the IteratorAggregate interface like the iterator interface, extends from traversable but unlike the Iterator interface it has only one method getIterator():
IteratorAggregate extends Traversable { /* Methods */ abstract public Traversable getIterator ( void ) }
IteratorAggregate does the same functionality of the Iterator interface but by implementing only one method getIterator() as shown in this example:
<?php class myData implements IteratorAggregate { public $places = ["USA", "London", "Berlin", "Paris"]; public function __construct() { } public function getIterator() { return new ArrayIterator($this->places); } } $obj = new myData; foreach($obj as $key => $value) { var_dump($key, $value); echo "\n"; } ?>
As shown above we created an array $places and implement method getIterator() which returns ArrayIterator from the provided array. getIterator() do exactly the same logic for the five methods which we described previously in the Iterator interface.
Converting Iterators to Arrays:
You can easily convert an Iterator object to array using iterator_to_array() function which takes an iterator and returning array:
<?php $iterator = new ArrayIterator(array('recipe'=>'pancakes', 'egg', 'milk', 'flour')); var_dump(iterator_to_array($iterator, true)); var_dump(iterator_to_array($iterator, false)); ?>
Conclusion
In this tutorial we described iterators and what their purpose, Demonstrated Traversable the base interface for iterators and the iterable data type. Also we learned about the Iterator and IteratorAggregate interfaces and how to implement them. In the next part we will describe some of the SPL iterators.
don