Shortest Answer
Extensibility & abstraction.
Abstract Answer
As soon as you have the ArrayAccess
interface, you've got things that aren't arrays but have an array interface. How will you traverse these? You could do it directly, which is where the Iterator
interface comes from. Iterator
might not make sense for some classes, either due to the single-responsibility principle, or for performance's sake, which is where you get IteratorAggregate
.
SPL-based Answer
SPL introduced a number of data structures. Iterators allow these to be traversed in foreach
loops. Without iterators, collections would need to be converted to arrays, a potentially costly operation.
Long Answer
Source-Iterators
The first use comes up with data sources (e.g. collections), which aren't all natively held in arrays. Examples (note: there is some overlap):
- trees
- the file system
- the previously mentioned SPL data structures
- network communications
- database query results
- external process results
- ongoing computation (PHP 5.5 introduces generators for this case)
Any collection that isn't array-based typically either is an iterator or has a corresponding iterator. Without iterators, each of the above would need to be converted to or collected in an array, which might incur heavy time & space costs. If you only had arrays available for iteration, the process can't proceed until the conversion/collection finishes. Iterators allow for partial results to be processed as they become available, and for only portions of collections to be in memory at any point in time.
In particular case outlined in the question, the UserEntityManager::getAll()
method could benefit from an Iterator
by reducing memory usage. Depending on what is used for data storage, an Iterator
will allow just some user records to be processed at a time, rather than loading all at once.
ArrayIterator
, DirectoryIterator
, and the SPL data structures are all examples of source-iterators (i.e. they iterate over a data source).
Processing-Iterators
Another use for iterators is in-place data processing. Processing iterators wrap other iterators, which allows for iterator composition. In PHP, these are the OuterIterator
s and sometimes have 'IteratorIterator' in their names.
You might ask "Why not just use functions?" The answer is that you could, but iterator composition (like function composition) is another (powerful) tool that allows for different types of solutions, sometimes achieving better performance or clarity. In particular, functions become a choke point in PHP, since it doesn't have in-language concurrency. Functions must finish before returning a result, which can be costly in terms of time & space, just as using arrays for iteration can be costly. Shallow.
The choke-point could be side-stepped by returning an iterator from a function, but that places a function call in between each iterator. Iterator composition allows deep iterator-based computations, cutting out the middle-man.
As for use-cases, consider a batch processing system that consumes data from multiple feeds, all of which have different formats. An adapting iterator can normalize the data for processing, allowing a single batch processor to service all the feeds.
As a reality check, in PHP you typically don't go full iterator-style any more than you'd write full-FP style, though PHP supports it. You usually don't compose more than a few iterators at a time (just as you often don't compose more than a few functions at a time in languages with function composition), and you don't create numerous iterators instead of functions.
RecursiveIteratorIterator
is an example of a processing iterator; it linearizes a tree (simplifying tree traversal).
Iterator & Functional Styles
Iterator composition allows for a style closer to functional programming. At its most basic, an iterator is (roughly) a sequence. In FP, the most basic operation is fold
(aka reduce
), though others (especially append
/concat
, filter
and map
) are often implemented natively rather than in terms of fold
for performance. PHP supports a few sequence operations on iterators (usually as OuterIterator
s); many are missing, but are easy to implement.
append
: AppendIterator
cons
: nothing, but easily (though not efficiently) implemented by creating an iterator that takes a single value, converting it to a single-element sequence, along with AppendIterator
. EmptyIterator
represents the empty sequence.
filter
: CallbackFilterIterator
convolute
(aka zip
): MultipleIterator
slice
: LimitIterator
map
- nothing, but easily implemented
fold
: nothing. Using a foreach
loop and accumulating a value in a variable is probably clearer than implementing fold
, but if you find a reason to do so, it's also straightforward (though probably not as an iterator).
flat-map
: nothing. Can be written fairly easily (though not efficiently) in terms of append
and map
.
cycle
: InfiniteIterator
unfold
: generators (which are, in general, just a special case of iterators).
- memoization:
CachingIterator
. Not so much a sequence operation as an (FP language) feature for function results.
The Future
Part of any language design is considering what the language could be. If concurrency were ever added to PHP, code that uses iterators (especially processing-iterators) could be made concurrent without being changed by making the iterators themselves concurrent.