Chapter 6. Iterators and SPL

Iteration is a key component of PHP programming. Whenever you loop through the elements of an array, the rows in a database, the files in a directory, or the lines of an HTML table, that's iteration.

Iteration takes many forms in PHP, but the most popular two are foreach and while loops. For example:

foreach ($array as $key => $value) {

    // iterate through array elements

}



reset($array);

while (list($key, $value) = each($array)) {

    // iterate through array elements

}

Although both of these constructs solve the same problem, foreach is shorter and easier. Iteration using a while loop can also introduce subtle bugs into your code, like in this example:

// $path is valid and readable

$dir = opendir($path); 

while ($file = readdir($dir)) {

    print "$file\n";

}

closedir($dir);

This code opens and iterates through files in a directory. When there are no more files, readdir( ) returns false, the while ends, and the directory is closed. It works perfectly.

It works perfectly, that is, until someone places a file named 0 in the directory. Then, $file is set to 0, and the loop terminates early because while (0) evaluates as false. Oops.

The correct way to read files from a directory is as follows:

while (false !=  = ($file = readdir($dir))) {

    print "$file\n";

}

You need to do a strict equality check using the = = = operator. Even = = gives you the wrong results because PHP autoconverts $file from a string to a Boolean.

Since there's no reason for you (or anyone else) to remember this, PHP 5 has an improved foreach that iterates through certain types of objects in a controlled manner. These objects are called iterators.

Iterators help eliminate problems in your code. For instance, the Standard PHP Library (SPL) extension provides a DirectoryIterator:

foreach (new DirectoryIterator($path) as $file) {

    print "$file\n";

}

This example produces the same results as the earlier code that uses the directory functions, but this code is shorter and safer because you cannot forget the = = = comparison.

SPL contains both standalone iterators and iterators meant to be chained together in a sequence. For example, you can pipe the output of a DirectoryIterator to an iterator that discards files that don't end in .html. This design helps you create a series of modular iterators that you can quickly combine to solve tasks.

SPL is bundled with PHP 5 and enabled by default, so it's essentially part of PHP 5 itself. Table 6-3 at the end of this chapter has a complete overview of all the different classes with Iterator in their name, including some not covered in this chapter.

Despite all these useful iterators, you're bound to want additional ones. Fortunately, iterators don't have to be coded in C. You can easily code iterators in PHP, for looping through LDAP or IMAP listings, or even cycling through results fetched from Amazon.com using REST.

You can also embed an iterator inside a class using aggregation. When the object is iterated inside a foreach loop, the embedded iterator is used, effectively turning your objects into iterators.

For example, you can iterate over SQLite query results without explicitly fetching each row:

$db = new SQLiteDatabase($database);

foreach($db->query($sql) as $row) {

    // operate on $row 

}

The SQLiteResult class has a built-in iterator that calls the object's fetch( ) method during iteration, and you can do the same for your own PHP classes. Normally, when you iterate over an object with foreach, PHP cycles through the object's properties. In PHP 5, you can override this behavior and make PHP loop over whatever you decide.

This chapter starts with iteration basics, by first showing how to iterate through a directory using PHP 4 and then comparing that to PHP 5's DirectoryIterator iterator. Afterward, there's a version of DirectoryIterator written in PHP.

The next iterator discussed in this chapter simplifies an example from earlier in the book: handling the results of MySQL multi-queries. Without an iterator, navigating through multi-query results is tricky, but this challenge is made simple using MySQLiQueryIterator.

Iterators don't have to work alone. You can combine them to filter and limit results. SPL has meta-iterators, or iterators that need to be fed another iterator to be useful.

For instance, using the FilterIterator in conjunction with a DirectoryIterator allows you to eliminate files that don't end in .html from your directory output.

Then there's the LimitIterator, which allows you to "page" through an iterator's results, using the same syntax as the SQL LIMIT clause. Combining it with the SimpleXMLIterator lets you redeploy your database logic onto XML documents.

After regular iterators, the chapter moves on to recursive iterators. Normal iterators only work for a single list of items, but it's quite common to have lists within lists. Directories contain files, but they can also contain other directories. To recurse through multiple levels, you need to upgrade your iterator to handle this complexity.

SPL's RecursiveDirectoryIterator class solves this task. Again, this iterator is recreated in PHP, to demonstrate the additional methods.

Once that's complete, there's a slight shift as this chapter moves back to traditional array and object iteration. PHP 5 brings a few changes to these old favorites to account for property access restrictions.

The chapter then shows how to implement the IteratorAggregate method. This lets a class return a custom iterator that you control, instead of using PHP's default behavior.

Finally, the last section lists all the SPL classes and interfaces, so you can get a bird's-eye view of the material and see what else SPL has to offer.