PHP5 allows foreach to work on implementers of Iterator or another interface called IteratorAggregate. A class that implements IteratorAggregate is a class that contains child elements that can be traversed by an Iterator. In this case, we have a Collection class containing elements that can be iterated by a CollectionIterator. To be able to use foreach on the Collection class, you'll need to modify Collection to implement this very simple interface, the definition for which follows.
<?php /** * Interface to create an external Iterator. */ interface IteratorAggregate implements Traversable { /** * Return an Iterator for the implementing object. */ function getIterator(); } ?>
Just as with the Iterator and Traversable interfaces, the IteratorAggregate interface is built in to PHP and you should not redeclare it in your own files.
IteratorAggregate requires only one method, getIterator(). For a class that implements this interface, this function returns an Iterator that is capable of traversing its elements. By modifying Collection to implement this interface, you'll be able to use foreach on it directly.
Modify the class declaration and include an implementation of the one method required by the IteratorAggregate interface:
<?php class Collection implements IteratorAggregate { //...[snip] the original parts of the Collection class //removed for brevity public function getIterator() { $this->_checkCallback(); return new CollectionIterator($this); } } ?>
Remember that the Collection class implements the idea of lazy instantiation. Don't forget to check for a load callback before returning the CollectionIterator. You need to make sure that the data has been populated before you can iterate over it.
With the CollectionIterator class created and the Collection class modified to implement IteratorAggregate, you can now legitimately run the block of code shown at the end of the previous chapter:
<?php $objStudent = StudentFactory::getStudent(12345); //get student #12345 foreach($objStudent->courses as $key => $objCourse){ print $objStudent . ' is currently enrolled in ' . $objCourse . "<br>\ n"; } ?>
Behind the scenes internally in PHP, the functions are invoked like this:
<?php $objStudent = StudentFactory::getStudent(12345); $itCourses = $objStudent->courses->getIterator(); for($itCourses->rewind(); $itCourses->hasMore(); $itCourses->next()) { $key = $itCourses->key(); $objCourse = $itCourses->current(); print $objStudent . ' is currently enrolled in ' . $objCourse . "<br>\n"; } ?>
If you need to create iterators for other classes you might create, it's important to understand how the methods of the Iterator and IteratorAggregate interfaces are invoked. When the operand of the foreach statement is an IteratorAggregate, PHP invokes the getIterator() method. The Iterator's internal pointer is reset using rewind(). Although there are still more elements to be seen (hasMore()), variables are created to hold both the current key and the current member element, and the rest of the foreach code block is then executed. The pointer is advanced (next()) and execution continues until we hit the end of the content (when hasMore() returns false).