An iterator is a special type of class that allows you to traverse a container or list. The keyword here is traverse. What this means is that the iterator provides the means to go through a list, but it does not perform the traversal itself.
The SPL provides a rich assortment of generic and specialized iterators designed for different contexts. The ArrayIterator, for example, is designed to allow object-oriented traversal of arrays. The DirectoryIterator is designed for filesystem scanning.
Certain SPL iterators are designed to work with others, and add value. Examples include FilterIterator and LimitIterator. The former gives you the ability to remove unwanted values from the parent iterator. The latter provides a pagination capability whereby you can designate how many items to traverse along with an offset that determines where to start.
Finally, there are a series of recursive iterators, which allow you to repeatedly call the parent iterator. An example would be RecursiveDirectoryIterator which scans a directory tree all the way from a starting point to the last possible subdirectory.
ArrayIterator class. It's extremely easy to use. All you need to do is to supply an array as an argument to the constructor. After that you can use any of the methods that are standard to all SPL-based iterators, such as current(), next(), and so on.$iterator = new ArrayIterator($array);
<ul> and <li> tags:function htmlList($iterator)
{
$output = '<ul>';
while ($value = $iterator->current()) {
$output .= '<li>' . $value . '</li>';
$iterator->next();
}
$output .= '</ul>';
return $output;
}ArrayIterator instance into a simple foreach() loop:function htmlList($iterator)
{
$output = '<ul>';
foreach($iterator as $value) {
$output .= '<li>' . $value . '</li>';
}
$output .= '</ul>';
return $output;
}CallbackFilterIterator is a great way to add value to any existing iterator you might be using. It allows you to wrap any existing iterator and screen the output. In this example we'll define fetchCountryName(), which iterates through a database query which produces a list of country names. First, we define an ArrayIterator instance from a query that uses the Application\Database\Connection class defined in Chapter 1, Building a Foundation:function fetchCountryName($sql, $connection)
{
$iterator = new ArrayIterator();
$stmt = $connection->pdo->query($sql);
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$iterator->append($row['name']);
}
return $iterator;
}nameFilterIterator(), which accepts a partial country name as an argument along with the ArrayIterator instance:function nameFilterIterator($innerIterator, $name)
{
if (!$name) return $innerIterator;
$name = trim($name);
$iterator = new CallbackFilterIterator($innerIterator,
function($current, $key, $iterator) use ($name) {
$pattern = '/' . $name . '/i';
return (bool) preg_match($pattern, $current);
}
);
return $iterator;
}LimitIterator adds a basic pagination aspect to your applications. To use this iterator, you only need to supply the parent iterator, an offset, and a limit. LimitIterator will then only produce a subset of the entire data set starting at the offset. Taking the same example mentioned in step 2, we'll paginate the results coming from our database query. We can do this quite simply by wrapping the iterator produced by the fetchCountryName() method inside a LimitIterator instance:$pagination = new LimitIterator(fetchCountryName( $sql, $connection), $offset, $limit);
ArrayIterator is processed by a FilterIterator, which in turn is limited by a LimitIterator. First we set up an instance of ArrayIterator:$i = new ArrayIterator($a);
ArrayIterator into a FilterIterator instance. Note that we are using the new PHP 7 anonymous class feature. In this case the anonymous class extends FilterIterator and overrides the accept() method, allowing only letters with even-numbered ASCII codes:$f = new class ($i) extends FilterIterator {
public function accept()
{
$current = $this->current();
return !(ord($current) & 1);
}
};FilterIterator instance as an argument to LimitIterator, and provide an offset (2 in this example) and a limit (6 in this example):$l = new LimitIterator($f, 2, 6);
range('A', 'Z'):function showElements($iterator)
{
foreach($iterator as $item) echo $item . ' ';
echo PHP_EOL;
}
$a = range('A', 'Z');
$i = new ArrayIterator($a);
showElements($i);FilterIterator on top of an ArrayIterator:$f = new class ($i) extends FilterIterator {
public function accept()
{
$current = $this->current();
return !(ord($current) & 1);
}
};
showElements($f);F H J L N P, which demonstrates a LimitIterator that consumes a FilterIterator, which in turn consumes an ArrayIterator. The output of these three examples is as follows:$l = new LimitIterator($f, 2, 6); showElements($l);

ArrayIterator instance populated with data from the query:function fetchAllAssoc($sql, $connection)
{
$iterator = new ArrayIterator();
$stmt = $connection->pdo->query($sql);
while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$iterator->append($row);
}
return $iterator;
}ArrayIterator instance inside RecursiveArrayIterator. Unfortunately, this approach only performs a shallow iteration, and doesn't give us what we want: an iteration through all elements of the multi-dimensional array that is returned from a database query:$iterator = fetchAllAssoc($sql, $connection); $shallow = new RecursiveArrayIterator($iterator);
RecursiveIteratorIterator.RecursiveArrayIterator class to work overtime and perform a deep iteration through all levels of the array:$deep = new RecursiveIteratorIterator($shallow);
As a practical example, you can develop a test script which implements filtering and pagination using iterators. For this illustration, you could call the chap_03_developing_functions_filtered_and_paginated.php test code file.
First of all, following best practices, place the functions described above into an include file called chap_03_developing_functions_iterators_library.php. In the test script, be sure to include this file.
The data source is a table called iso_country_codes, which contains ISO2, ISO3, and country names. The database connection could be in a config/db.config.php file. You could also include the Application\Database\Connection class discussed in the previous chapter:
define('DB_CONFIG_FILE', '/../config/db.config.php');
define('ITEMS_PER_PAGE', [5, 10, 15, 20]);
include (__DIR__ . '/chap_03_developing_functions_iterators_library.php');
include (__DIR__ . '/../Application/Database/Connection.php');Next, you can process input parameters for the country name and the number of items per page. The current page number will start at 0 and can be incremented (next page) or decremented (previous page):
$name = strip_tags($_GET['name'] ?? ''); $limit = (int) ($_GET['limit'] ?? 10); $page = (int) ($_GET['page'] ?? 0); $offset = $page * $limit; $prev = ($page > 0) ? $page - 1 : 0; $next = $page + 1;
Now you're ready to fire up the database connection and run a simple SELECT query. This should be placed in a try {} catch {} block. You can then place the iterators to be stacked inside the try {} block:
try {
$connection = new Application\Database\Connection(
include __DIR__ . DB_CONFIG_FILE);
$sql = 'SELECT * FROM iso_country_codes';
$arrayIterator = fetchCountryName($sql, $connection);
$filteredIterator = nameFilterIterator($arrayIterator, $name);
$limitIterator = pagination(
$filteredIterator, $offset, $limit);
} catch (Throwable $e) {
echo $e->getMessage();
}Now we're ready for the HTML. In this simple example we present a form that lets the user select the number of items per page and the country name:
<form>
Country Name:
<input type="text" name="name"
value="<?= htmlspecialchars($name) ?>">
Items Per Page:
<select name="limit">
<?php foreach (ITEMS_PER_PAGE as $item) : ?>
<option<?= ($item == $limit) ? ' selected' : '' ?>>
<?= $item ?></option>
<?php endforeach; ?>
</select>
<input type="submit" />
</form>
<a href="?name=<?= $name ?>&limit=<?= $limit ?>
&page=<?= $prev ?>">
<< PREV</a>
<a href="?name=<?= $name ?>&limit=<?= $limit ?>
&page=<?= $next ?>">
NEXT >></a>
<?= htmlList($limitIterator); ?>The output will look something like this:

Finally, in order to test the recursive iteration of the country database lookup, you will need to include the iterator's library file, as well as the Application\Database\Connection class:
define('DB_CONFIG_FILE', '/../config/db.config.php');
include (__DIR__ . '/chap_03_developing_functions_iterators_library.php');
include (__DIR__ . '/../Application/Database/Connection.php');As before, you should wrap your database query in a try {} catch {} block. You can then place the code to test the recursive iteration inside the try {} block:
try {
$connection = new Application\Database\Connection(
include __DIR__ . DB_CONFIG_FILE);
$sql = 'SELECT * FROM iso_country_codes';
$iterator = fetchAllAssoc($sql, $connection);
$shallow = new RecursiveArrayIterator($iterator);
foreach ($shallow as $item) var_dump($item);
$deep = new RecursiveIteratorIterator($shallow);
foreach ($deep as $item) var_dump($item);
} catch (Throwable $e) {
echo $e->getMessage();
}Here is what you can expect to see in terms of output from RecursiveArrayIterator:

Here is the output after using RecursiveIteratorIterator:
