This topic’s stayed in my mind for a while. Inspired by Joshua Thijssen’s presentation from PHP UK about (re)discovering the SPL in PHP, I decided to investigate this more carefully. I have to admit that took me some time to understand how the things work and how to not misunderstood the purposes of each iterator and because of lack of documentation it wasn’t that easy. I did a couple of mistakes and probably I will do more, but as Joshua said in his presentation:
The documentation of SPL is completely useless. What can we do? Blog about it!
So, brave yourself. Here my blogpost comes!
Today, I’d like to focus only on one part of documentation – the iterators, because it’s quite attractive how you can solve problems with using them. The main advantage to use the iterators in PHP is an easy way to achieve reusability, single responsibility and nice looking architecture with design patterns. I’d like to show you how quick some of common problems can be solved by using iterators and then, check if that does any impact on the performance.
Design Patterns
Connecting iterators – Decorator
The decorator pattern allows to add a behaviour to individual object, without affecting behaviour of other objects from the same class. As an example we can consider the price calculation for the billing in e-commerce shop where you need to add some taxes, ship price or duty to the final price.
We can split our requirements and create a single responsibility classes for each prize calculation. The third party object will decide which calculations should be included to the final price. The code will look like this:
1 2 3 4 5 6 7 8 | $price = new RegularPrice(120); $price = new TaxPrice($price); $price = new ShipmentPrice($price); if ($dutyRequired) { $price = new DutyPrice($price); } echo $price->getPrice(); |
The previous object is passed as an argument to the next one. When the getPrice method is called then this should call the same method from the passed object. In the result, we have a price calculated by all the objects.
The iterators work the same way in out case. We create a main object which is called ArrayIterator and the add a custom Filters, Iterators, Parsers etc.
1 2 3 4 | $arrayIterator = new ArrayIterator(['apple', 'orange', 'beans', 'pumpkin', 'onion']); $arrayIterator = new RandomizeArrayIterator($arrayIterator); $arrayIterator = new FruitIterator($arrayIterator); $arrayIterator = new LimitIterator($arrayIterator, 0, 3); |
FilterIterator
As you can see, it’s not so hard to deliver objects responsible only for one thing. One of the most useful iterator is FilterIterator in my opinion which is extended by FruitIterator in the example. The code might look like:
1 2 3 4 5 6 7 8 9 |
All necessary things there are placed in accept method which returns a bool value determines if the record can be attached or not. On the other hand, we do not need to implement a new class every time. We can use the CallbackFilterIterator and just attach the closure.
1 2 3 | $fuits = new CallbackFilterIterator($arrayIterator, function ($current, $key, $iterator) use ($acceptedFruits) { return in_array($current, $acceptedFruits) }); |
Aggregate Pattern
An Aggregate pattern can refer to concepts in either statistics or computer programming. Both uses deal with considering a large case as composed of smaller, simpler, pieces. This rather refers to an object such as a list, vector, or generator which provides an interface for creating iterators.
So, let’s create a class Salad which can contain ingredients (from previous case). A standard code will look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Salad { protected $ingredients; public function __construct(array $ingredients) { $this->ingredients = $ingredients; } public function getIngredients() { return $this->ingredients; } } $greekSalad = new Salad(['Romaine lettuce', 'Onion', 'Black Olives', 'Green bell pepper', 'Red bell pepper', 'Tomatoes', 'Cucumber', 'Feta cheese', 'Olive Oil', 'Oregano', 'Lemon']); foreach ($greekSalad->getIngredients() as $ingredient) { echo $ingredient; } |
But, we can use a standard component from SPL library as IteratorAggregate and our code will change a little bit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php class Salad implements IteratorAggregate { protected $ingredients; public function __construct(array $ingredients) { $this->ingredients = $ingredients; } function getIterator() { return new ArrayIterator($this->ingredients); } } $greekSalad = new Salad(['Romaine lettuce', 'Onion', 'Black Olives', 'Green bell pepper', 'Red bell pepper', 'Tomatoes', 'Cucumber', 'Feta cheese', 'Olive Oil', 'Oregano', 'Lemon']); foreach ($greekSalad as $ingredient) { echo $ingredient; } |
This can be quite useful, but only when your classes are kept small – so remember about the SOLID principles there.
Performance
Everything looks promising until now. Two examples above show us how to change ugly foreach or for loops into smart looking object oriented architecture. But the main question is – is that so clever? I have checked and the result surprised me.
I have created two pieces of code to check which method is faster – using full object iteration with SPL, or ugly loops.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class RandArrayIterator extends ArrayIterator { public function current() { return rand(0, 100); } } class OddIterator extends FilterIterator { public function accept() { return 0 == $this->getInnerIterator()->current()%2; } } $arrayIterator = new ArrayIterator(['']); $arrayIterator = new RandArrayIterator($arrayIterator); $arrayIterator = new InfiniteIterator($arrayIterator); $arrayIterator = new LimitIterator($arrayIterator, 0, $argv[1]); $arrayIterator = new OddIterator($arrayIterator); foreach ($arrayIterator as $a){} |
Both do exactly the same. Generate a big array with random values and choose only odd results. I run them for 1 000 000 records and the execution time surprised me. I wasn’t prepared for such difference:
Loop: 1,42 s
SPL: 7,82 s
Nothing says more than raw numbers. How pretty SPL Iterators may be, they cannot be such fast as the standard loop. The difference is just astonishing, so I will be careful with using Iterator since now.
The frosting on the cake
Read more
[1] PHP Documentation – SPL Iterators
[1] PHP Iterator Design Pattern I: Access to Aggregates
[2] SitePoint – Using SPL Iterators, Part 1
[3] SitePoint – Using SPL Iterators, Part 2
[4] The Standard PHP Library – SPL
[5] SPL iterators with closure context switching
[6] Introduction to SPL