My purpose here is to talk about the decorator pattern in SPL iterators. But since many people are not aware of how much the iterators are really used, I would also like to point out one of their use cases in real life. So, my starting point will be AWS Simple Storage Service (S3). For those not familiar at all with Amazon Web Services (AWS), S3 is a cloud storage service. To put it simple, instead of saving files locally and managing them through the well known PHP functions (file_put_contents, fread, unlink, makedir,…) , we are saving them to the cloud (remote storage) and we are managing them through an API (using an HTTP requests for every action). AWS S3 provides a PHP library, namely AWS-SDK-PHP (the documentation of the v3 of this API can be found here), to make the use of this API much easier. In S3 terminology, you can think of “buckets” as directories and “data objects” as files (a bit simplified explanation for the purpose of this article).
So, listing the objects inside a bucket can be done like this:
$s3 = $this->getS3Client(); // $s3 will be an Aws\S3\S3Client instance $objectIterator = $s3->getIterator('ListObjects', [ 'Bucket' => $bucket_name ]); echo "<p></p><strong>Bucket contents:</strong></p><pre>"; foreach ($objectIterator as $object) { echo "Key: ".$object['Key']." - Size: ".$object['Size']."<br>"; }
Our sample result is:
The interesting thing here is the return type of the getIterator() method. It is an SPL Iterator!
SPL iterators is a very typical example of a case where the decorator pattern is being heavily used. The top-level interface that is implemented by both base classes and (sub)decorators is the Iterator class. When we say base classes, we mean classes like the ArrayIterator. When we say decorators, we mean classes like the FilterIterator or the CallbackFilterIterator class. The difference between these two should be obvious for those familiar with the Decorator pattern, but let say shortly that all decorators here expect as input to their constructor an implementation of the Iterator interface. And they produce as output, again an implementation of the Iterator interface. Decorators are adding functionality to the input class without affecting the way this class can be treated (the object that you get as output implements the same interface as the input object).
As an example, let say that we need to list only larges files and we define large files those with size greater than 35.000 bytes. We can filter the bucket contents by using the SPL CallbackFilterIterator to “decorate” our original iterator:
$s3 = $this->getS3Client(); $objectIterator = $s3->getIterator('ListObjects',[ 'Bucket' => $bucket_name ]); $large_files = new CallbackFilterIterator($objectIterator, '<the namespace of this class>\is_large_file'); echo "<p></p><strong>Bucket contents:</strong></p><pre>"; /** @var Aws\Result */ foreach ($large_files as $object) { echo "Key: ".$object['Key']." - Size: ".$object['Size']."<br>"; } // Let's assume that this function is defined in the file as the class where the rest of the code belongs function is_large_file($s3Object) { return $s3Object['Size'] > 35000; }
Now, the result of this code will be: