This article is part of a series on design patterns for framework independent code, especially to write extensions for Magento 1 and Magento 2 alike.


All Parts:


In Part 3 (Building Bridges) I explained how we can access Magento models in our framework independent library and also gave the example of a repository interface to access collections. But in some cases you don’t want to load the collection immediately or not even load all items at once. In Magento, if you don’t call load() on a collection explicitly, it gets triggered as soon as you start iterating over the it with foreach. And for the other case there’s the iterator resource model to load the result of a query row by row, which can be useful for processing a huge amount of data.

In Magento 2, the Repository APIs (like Magento\Catalog\Api\ProductRepositoryInterface) return result objects (Magento\Catalog\Api\Data\ProductSearchResultsInterface) which contain an array of all items. The collection classes are only used internally by the repositories and there is no lazy loading anymore if you use this API.

Now what we need is a way to make use of these optimizing features transparently, if possible, but it should be an implementation detail that we leave upon the concrete framework dependent implementation. This requires us to change repository interfaces from this:

to this:

where ProductIterator is a new interface:

Note that this minimal interface just tells us that it’s an iterator where each element is an instance of Product. The interface extends the internal Iterator interface. current() is already part of this interface, the only addition here is the phpDoc return type hint.

You could add more methods if you need them, for example count() from the Countable interface or accessors like getFirstItem():

Implementation: Magento 1

Now a Magento 1 implementation that makes use of deferred loading is possible. We can use the IteratorIterator class which is basically a wrapper for other iterators. It implements Iterator, not IteratorAggregate, so that it fulfils our ProductIterator interface, but it can also wrap IteratorAggregate implementations.

IteratorIterator

This iterator will take the Magento collection as constructor argument and we can access the internal iterator of the collection with getInnerIterator(). Not the collection itself, which implements IteratorAggregate, this is important to get access to the iterator methods like current().

Here we override current() to convert each product to an instance of the product bridge class, everything else is handled by the IteratorIterator base class. current() is never implicitly called when there is no current element, so we can assume that $magentoProduct is a product model instance. The corresponding ProductRepository implementation can look like this:

Implementation: Magento 2

Or for Magento 2, without deferred loading:

The iterator extends ArrayIterator which is the simplest implementation if you operate on a complete array. We just convert the Magento product model to our bridge in the current() method.

And the corresponding repository implementation:

Note that I omitted the “use” statements in all Magento 1 examples, but for Magento 2 they are important to distinguish core classes from our library classes. Also to not overcomplicate the example, I instantiate the bridge classes Product and ProductIterator directly instead of injecting the generated factories that Magento 2 provides. To follow best practices and allow extension via plugins, you should change that.

Lazy Loading Product Iterator

To demonstrate how powerful this abstraction is, I’ll show you parts of our product iterator implementation that we use for the indexer in IntegerNet_Solr (Magento 1). It loads the product collection in chunks (default chunk size: 1000) to reduce the memory footprint, but it’s totally transparent so that the library that uses it can use a single foreach to iterate over all products.

What happens here? First, the OuterIterator interface is an interface that you can use to implement your own variations of IteratorIterator as described above. There is nothing special about it, you still have to implement the methods from Iterator, but it adds a getInnerIterator() method for access to the actual iterator. The code would work as well if we just implemented Iterator and made getInnerIterator() private.

Internally, rewind() is called when you start iterating, and valid() to check if there is another element.

  • rewind() creates the first collection and sets the inner iterator to the collection iterator
  • valid() creates a new collection with increased page number if the current collection doesn’t have any more elements, replaces the current collection iterator and checks if this one has any more elements. Only if this result is empty, the iterator stops.

Conclusion

When dealing with collections of objects, use a collection interface, which should extend PHP’s Iterator interface, instead of plain old arrays. Understand and use the power of PHP SPL iterators. If this is new territory for you, have a look at the Iterating PHP Iterators book by Cal Evans, since the online manual is quite sparse on the various iterators. I have to admit, I did not read it, but I trust Cal enough to recommend it blindly.


The End?

This is the last part of the series – at least for now. If you followed me from the beginning, thank you for your patience! I hope I was able to shed some light on the topic. Please leave a comment if you have any critique or additions. Your feedback is always welcome, I’m open to discuss any points made here in these blog posts and I’m happy about any opportunity to learn from other people’s experiences.

Fabian Schmengler

Author: Fabian Schmengler

Fabian Schmengler is Magento developer and trainer at integer_net. His focus lies in backend development, conceptual design and test automation.

Fabian was selected repeatedly as a Magento Master in 2018 based on speaking engagements, active participation on StackExchange and contributions to the Magento 2 core.

More Information · Twitter · GitHub