3

I'm trying to figure out what's the actual benefit of using Iterator classes in Object Oriented PHP over the standard array.

I'm planning to upgrade my framework by converting all arrays to object, but I just don't understand the actual need apart from having the system being fully OOP.

I know that by the use of IteratorAggregate I can create:

class MyModel implements IteratorAggregate {

    public $records = array();

    public function __construct(array $records) {

        $this->records = $records;

    }

    public function getIterator() {

        return new ArrayIterator($this->records);

    }

}

and then simply loop through it like using the array:

$mdlMy = new MyModel(array(
    array('first_name' => 'Mark', 'last_name' => 'Smith'),
    array('first_name' => 'John', 'last_name' => 'Simpson')
));



foreach($mdlMy as $row) {

    echo $row['first_name'];
    echo $row['last_name'];

}

Could someone in simple terms explain the actual purpose of these - perhaps with some use case.

1
  • Saving memory, mostly. They don't need to hold the whole - possibly huge - array in memory. You can process one item at a time. Commented Aug 14, 2015 at 9:55

2 Answers 2

2

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 OuterIterators 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 OuterIterators); 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.

Sign up to request clarification or add additional context in comments.

Comments

0

Like this you have no advantage. Yet. But as soon as you need to implement some new functionality, you are having fun!

If you want to add a record to your 'array', but need to do some checking first, you'll have that in all instances: you don't have to find all the places you use this array, but you'll just add your validation to the constructor.

All other things that could go in a model will be your advantage.

But beware, 'OOP' is not "I'm using objects me!". It is also about what objects you have, what they do, etc. So don't just go packing all your arrays in objects and call it "going fully OOP". There's nothing OOP about that, nor is there a limit on using arrays in an OOP project.

5 Comments

So the actual, practical use is the centralized location - without looking for all instances of arrays you just amend one method and that will affect all instanced? I've started converting my models to accomodate EntityManager and then feed the separate Models, which do not contain any methods apart the constructor, which binds the passed array to the properties - that is in the case of a single record model, but I wanted to use the same EntityManager to contain methods to fetch a lists of records and pass them to the object for iteration.
You should look at it the other way around: with this interface you can have an object that you needed in the first place be an iterator so you can use foreach. Say you have an order-object that has several products. You might want to iterate over these products, each iteration giving you a product-object. That's the use of the iterator. What you have is not OOP, so the specific advantages of this are hard to see. For these users you might want to build a user-object (firstname/lastname) first, then worry about the array later (or not :) )
bottomline: don't add "object" as a magic sauce to be OOP. Make a data-model, make your objects, and if you need to itterate over those objects, then make it implement an iterator.
I'm not sure I understand well. I have an object for say User with properties $first_name, $last_name, $email, $password and constructor. I use UserEntityManager and it's getUser method that fetches the user record based on the $id passed as parameter. The same method returns the User object (which takes the record in the constructor and binds values to the properties). This is my UserModel and works with single object. For list of users I would like to have the UserIteratorModel, which is implemented in the right way - UserEntityManager has getAll() and returns UserIteratorModel with it.
With the above example I could presumably simply use RecursiveArrayIterator() to get the same result without necessarily creating the new method, which inherits from IteratorAggregate() ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.