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.