External Iterators in PHP Using OOP

In this first part of a short tutorial series, you will learn how to implement external iterators in PHP using OOP (Object Oriented Programming). This is useful when you want to construct easily traversable classes. Keep reading to learn more!

Very often, programming is described as a nifty mixture of proven methodologies and a certain dose of art (in this case, a simple euphemism for hard thinking), which is aimed at solving problems. While finding an adequate balance between these two factors is an extremely challenging process, even for the most experienced souls, the goal of programming is clear and straight: it must be used for solving problems, no matter their nature.

Of course, there’s no need to be a purist of pragmatism to realize that it’s literally impossible to solve a problem without the existence of certain elements present in nearly every programming language, such as conditionals and iterators. Although the use of the former can be reduced at some level by means of Polymorphism (at least in Object-Oriented Design), the latter are simply non-substitutable.

Actually, there’s nothing wrong with iterators, regardless of the implementation that each language makes of them. However, problems start to arise when they’re misused or implemented in the wrong places, specially in object-oriented environments. In many cases, classes are turned into structures (or containers, to stick with a more academic definition) capable of iterating over themselves without a solid foundation behind them, something that violates the Single Responsibility Principle (http://en.wikipedia.org/wiki/Single_responsibility_principle). After all, why should a class know how to traverse itself if its concerns are completely different?

Here’s exactly where the use of external iterators comes into play. But what are they, in simple terms? Well, in object-oriented programming, an external iterator is usually a separate object capable of traversing other objects or other structures. This gives programmers the ability to define classes that have fewer responsibilities, and that additionally can be traversed by an outer, more decoupled iterator.

Naturally, external iterators are language-agnostic, and as such, can be implemented in multiple programming languages, including PHP. What’s more, PHP makes it easy to create external iterators through its native “IteratorAggregate” SPL interface, which shopefully are familiar with. Given that, in this article series I’ll be setting up some concrete examples that will show you how to implement a few external iterators and how to use them for lazy-loading data.

The Anti-Pattern: Building a Hierarchy of Traversable Entity Classes in PHP

To demonstrate how to implement an external iterator (and why it’s necessary to build one sometimes), I’m going to set up a scenario that’s pretty common to see nowadays: simply put, there’s a fictional web application whose domain model is composed of a few anemic entities, which are simple data holders with some predefined constraints.

In such a context, there exists a base abstract class, which defines the structure and behavior of generic entities. The class in question, in its initial incarnation, looks like this: 

(Model/EntityAbstract.php)

<?php

namespace Model;

abstract class EntityAbstract implements Countable, Iterator
{
    protected $_allowedFields = array();
    protected $_fields = array();
   
    /**
     * Constructor
     */
    public function __construct(array $fields)
    {
        foreach ($fields as $name => $value) {
            $this->$name = $value;
        }
        // store the key/value pairs in the $_fields array (required for inner iteration)
        $this->_fields = $this->_toArray();
    }
   
    /**
     * Set a value for the specified field (via the mutator if it exists or by assigning it to the field)
     */
    public function __set($name, $value)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new OutOfRangeException(‘Setting the field ‘ . $name . ‘ is not allowed for this entity.’);
        }
        $mutator = ‘set’ . ucfirst(str_replace(‘_’, ”, $name));
        if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
            $this->$mutator($value);          
        }
        else if(property_exists($this, $name)) {
            $this->$name = $value;  
        }
        else {
            throw new InvalidArgumentException(‘The field ‘ . $name . ‘ does not exist for this entity.’);
        }
    }
   
    /**
     * Get the value of the specified field (via the getter if it exists or by getting it from the field)
     */
    public function __get($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new OutOfRangeException(‘Getting the field ‘ . $name . ‘ is not allowed for this entity.’);
        }
        $accessor = ‘get’ . ucfirst(str_replace(‘_’, ”, $name));
        if (method_exists($this, $accessor) && is_callable(array($this, $accessor))) {
            return $this->$accessor;   
        }
        if (property_exists($this, $name)) {
            return $this->$name;
        }
        throw new InvalidArgumentException(‘The field ‘ . $name . ‘ does not exist for this entity.’);   
    }
   
    /**
     * Passthrough for setting/getting field values
     */
    public function __call($name, $arguments)
    {
        if (strlen($name) > 3) {
            $property = strtolower(substr($name, 3));
            if (property_exists($this, $property)) {
                if (strpos($name, ‘set’) === 0) {
                    $this->$property = array_shift($arguments);
                    return $this;
                }
                if (strpos($name, ‘get’) === 0) {
                    return $this->$property;
                }
            }
        }
        throw new BadMethodCallException(‘The mutator/accessor ‘ . $name . ‘ does not exist for this entity.’);
    }
         
    /**
     * Create an array with the field/value pairs of the entity
     */
    protected function _toArray()
    {
        $fields = get_object_vars(($this));
        unset($fields['_allowedFields'], $fields['_fields']);
        return $fields;
    }

    /**
     * Count the number of entity fields (implementation required by Countable interface)
     */
    public function count()
    {
        return count($this->_fields);
    }
   
    /**
     * Reset the entity fields (implementation required by Iterator interface)
     */    
    public function rewind()
    {
        reset($this->_fields);
    }
   
    /**
     * Get the key of the current entity field (implementation required by Iterator interface)
     */
    public function key()
    {
        return key($this->_fields);
    }
   
    /**
     * Get the current entity field (implementation required by Iterator interface)
     */
    public function current()
    {
        return current($this->_fields);
    }

    /**
     * Check if there’re more entity fields (implementation required by Iterator interface)
     */
   
    public function valid()
    {
        return ($this->current() !== false);
    }
   
    /**
     * Move to the next entity field (implementation required by Iterator interface)
     */
    public function next()
    {
        next($this->_fields);
    }
}

Based partially on the abstract model developed by Court Ewing here (http://epixa.com/2010/05/the-best-models-are-easy-models) the above “EntityAbstract” class implements the “__set()”, “__get()” and “__call()” PHP magic  methods, in order to assign and retrieve the values assigned to the fields of an entity. Effectively, this “magic” is used for setting/retrieving those values via the corresponding mutators/getters, if they’ve been defined.

While this functionality is all well and fine, you may have noticed that the class also implements the native Countable and Iterator SPL interfaces. This (at least at a glance) makes it easy to traverse the protected/private members declared by any derivative of the class and count them as if they were array elements.   
  
To see if the class is actually traversable and countable, I’m going to spawn a concrete subclass, which will be tasked with modeling users. The implementation of this child class is as follows: 

(Model/User.php)

<?php

namespace Model;

class User extends EntityAbstract
{
    protected $_allowedFields = array(‘_id’, ‘_name’ , ‘_email’);
    protected $_id;
    protected $_name;
    protected $_email;  
   
    /**
     * Set the user’s ID
     */
    public function setId($id)
    {
        if(!filter_var($id, FILTER_VALIDATE_INT, array(‘options’ => array(‘min_range’ => 1, ‘max_range’ => 65535)))) {
            throw new OutOfRangeException(‘The ID ‘ . $id . ‘ is invalid.’);
        }
        $this->_id = $id;
    }
     
    /**
     * Set the user’s name
     */ 
    public function setName($name)
    {
        if (strlen($name) < 2 || strlen($name) > 32) {
            throw new OutOfRangeException(‘The name ‘ . $name . ‘ is invalid.’);
        }
        $this->_name = $name;
    }
     
    /**
     * Set the user’s email address
     */
    public function setEmail($email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new OutOfRangeException(‘The email address ‘ . $email . ‘ is invalid.’);
        }
        $this->_email = $email;
    }
}

In reality, there’s nothing specially difficult to grasp with reference to this brand new subclass, as it defines a few mutators that allow to set the ID, name and email address of generic users, according to some specific restrictions.

Now, if I wanted to give the previous “User” class a try, the following script would do the trick pretty neatly (note that the autoloader is the same used here [http://www.devshed.com/c/a/PHP/Building-a-service-Locator-in-PHP/]):

<?php

use ModelUser as User;

// include the autoloader
require_once ‘Autoloader.php';
Autoloader::getInstance();

// create a user object
$user = new User(array(
    ‘_id’    => 1,
    ‘_name’  => ‘Sandra Wilson’,
    ‘_email’ => ‘sandra@domain.com’
));

// count the number of fields of the user
echo ‘The number of fields of the user is ‘ . count($user) . ‘.<br />';

/* displays the following
The number of fields of the user is 3.
*/

// iterate over the user and display their value/field pairs
foreach ($user as $field => $value) {
    echo "The value assigned to the field ‘$field’ is $value.<br />";
}

/* displays the following
The value assigned to the field ‘_id’ is 1.
The value assigned to the field ‘_name’ is Sandra Wilson.
The value assigned to the field ‘_email’ is sandra@domain.com.
*/

That worked like a charm! As the above script shows, any instance spawned from the “User” class can be easily traversed via a PHP “foreach” loop construct and its protected fields can be also counted through the “count()” function. So, what’s so wrong with its abstract parent?

Well, I don’t want to sound like I’m spoiling the party here, but in its current implementation the base class is taking too many responsibilities, as it’s charged with defining the behavior of generic entities, but it also knows how to traverse itself. Definitively, it’d be much better to delegate the traversing/counting operations to an independent, external iterator class.

To prove the veracity of my statement, in the coming segment I’m going to refactor the previous “EntityAbstract” class, which this time will have fewer concerns, since the traversing functions will be implemented by an outer iterator.

To see how this will be done, keep reading. 

{mospagebreak title=Creating the External Iterator in PHP}   
Fixing Things Up: Using an External Iterator

As it was said in the preceding section, it’s necessary to offload to an outer iterator the traversing tasks currently performed by the base “EntityAbstract” class. To do so, the first step that must be taken is to refactor the class in question, whose source code now will look like this:

(Model/EntityAbstract.php)

<?php

namespace Model;
use Utility;

abstract class EntityAbstract implements Countable, IteratorAggregate
{
    protected $_allowedFields = array();
   
    /**
     * Constructor
     */
    public function __construct(array $fields)
    {
        foreach ($fields as $name => $value) {
            $this->$name = $value;
        }
    }
   
    /**
     * Set a value for the specified field (via the mutator if it exists or by assigning it to the field)
     */
    public function __set($name, $value)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new OutOfRangeException(‘Setting the field ‘ . $name . ‘ is not allowed for this entity.’);
        }
        $mutator = ‘set’ . ucfirst(str_replace(‘_’, ”, $name));
        if (method_exists($this, $mutator) && is_callable(array($this, $mutator))) {
            $this->$mutator($value);          
        }
        else if(property_exists($this, $name)) {
            $this->$name = $value;  
        }
        else {
            throw new InvalidArgumentException(‘The field ‘ . $name . ‘ does not exist for this entity.’);
        }
    }
   
    /**
     * Get the value of the specified field (via the getter if it exists or by getting it from the field)
     */
    public function __get($name)
    {
        if (!in_array($name, $this->_allowedFields)) {
            throw new OutOfRangeException(‘Getting the field ‘ . $name . ‘ is not allowed for this entity.’);
        }
        $accessor = ‘get’ . ucfirst(str_replace(‘_’, ”, $name));
        if (method_exists($this, $accessor) && is_callable(array($this, $accessor))) {
            return $this->$accessor;   
        }
        if (property_exists($this, $name)) {
            return $this->$name;
        }
        throw new InvalidArgumentException(‘The field ‘ . $name . ‘ does not exist for this entity.’);   
    }
   
    /**
     * Passthrough for setting/getting field values
     */
    public function __call($name, $arguments)
    {
        if (strlen($name) > 3) {
            $property = strtolower(substr($name, 3));
            if (property_exists($this, $property)) {
                if (strpos($name, ‘set’) === 0) {
                    $this->$property = array_shift($arguments);
                    return $this;
                }
                if (strpos($name, ‘get’) === 0) {
                    return $this->$property;
                }
            }
        }
        throw new BadMethodCallException(‘The mutator/accessor ‘ . $name . ‘ does not exist for this entity.’);
    }
         
    /**
     * Create an array with the field/value pairs of the entity
     */
    public function toArray()
    {
        $fields = get_object_vars(($this));
        unset($fields['_allowedFields']);
        return $fields;
    }
   
    /**
     * Get the external iterator
     */
    public function getIterator()
    {
        return new UtilityArrayCollection($this->toArray());
    }
   
    /**
     * Count the number of elements in the external iterator
     */
    public function count()
    {
        return count($this->getIterator());
    }   
}

Certainly, a few interesting details are worth noting here, with reference to the refactored version of this abstract class. The first one is that it no longer implements an inner iterator, which is a great improvement toward the application of the Single Responsibility Principle. The second one is that it uses an external iterator, called “ArrayCollection”, which is responsible for traversing the class’ protected members.

Considering that the class now implements the Countable and IteratorAggregate SPL interfaces, the outer iterator will be invoked transparently in a “foreach” loop or when calling explicitly its “getIterator()” method. In a few easy steps, I managed to create a class that employs an external element to traverse its data members.

And speaking of an outer iterator, I still haven’t showcased the definition of the aforementioned “ArrayCollection”. Well, this one is a simple wrapper for a native PHP array and its source code is as follows:
 
(Utility/ArrayCollection.php)

<?php

namespace Utility;

class ArrayCollection implements Countable, Iterator, ArrayAccess
{
    protected $_data = array();
   
    /**
     * Constructor
     */
    public function __construct(array $data = array())
    {
        if (!empty($data)) {
            $this->_data = $data;
        }
        $this->rewind();
    }
   
    /**
     * Count the number of elements in the collection (implementation required by Countable interface)
     */
    public function count()
    {
        return count($this->_data);
    }
   
    /**
     * Reset the collection (implementation required by Iterator interface)
     */    
    public function rewind()
    {
        reset($this->_data);
    }
   
    /**
     * Get the key of the current element in the collection (implementation required by Iterator interface)
     */
    public function key()
    {
        return key($this->_data);
    }
   
    /**
     * Get the current element in the collection (implementation required by Iterator interface)
     */
    public function current()
    {
        return current($this->_data);
    }

    /**
     * Check if there’re more elements in the collection (implementation required by Iterator Interface)
     */
   
    public function valid()
    {
        return ($this->current() !== false);
    }
   
    /**
     * Move to the next element in the collection (implementation required by Iterator interface)
     */
    public function next()
    {
        next($this->_data);
    }
   
    /**
     * Add an element to the collection (implementation required by ArrayAccess interface)
     */
    public function offsetSet($key, $value)
    {
        if (!isset($key)) {
            $this->_data[] = $value;
        }
        else {
            $this->_data[$key] = $value;
        }
        return true;
    }
   
    /**
     * Remove an element from the collection (implementation required by ArrayAccess interface)
     */
    public function offsetUnset($key)
    {
        if (isset($this->_data[$key])) {
            unset($this->_data[$key]);
            return true;
        }
        return false;
    }
   
    /**
     * Get the specified element in the collection (implementation required by ArrayAccess interface)
     */
    public function offsetGet($key)
    {
        return isset($this->_data[$key]) ?
               $this->_data[$key] :
               null;
    } 
   
    /**
     * Check if the specified element exists in the collection (implementation required by ArrayAccess interface)
     */    
    public function offsetExists($key)
    {
        return isset($this->_data[$key]);
    }

    /**
     * Return the elements in the collection
     */
    public function toArray()
    {
        return $this->_data;
    }
}

As it can be seen above, the “ArrayCollection” class is a basic implementation of a PHP array iterator, which also permits to manipulate the elements that it handles using an array-like notation, thanks to the contract imposed by the built-in ArrayAccess interface.

{mospagebreak title=Defining the Functionality of PHP Array Iterators}

Finally, to demonstrate the actual functionality of this external iterator, it’s necessary to derivate a subclass from the parent “EntityAbstract”. To keep things clear and easy to grasp, I’m going to reuse the previous “User” class, whose implementation remains exactly the same. Check it out:

(Model/User.php)

<?php

namespace Model;

class User extends EntityAbstract
{
    protected $_allowedFields = array(‘_id’, ‘_name’ , ‘_email’);
    protected $_id;
    protected $_name;
    protected $_email;  
   
    /**
     * Set the user’s ID
     */
    public function setId($id)
    {
        if(!filter_var($id, FILTER_VALIDATE_INT, array(‘options’ => array(‘min_range’ => 1, ‘max_range’ => 65535)))) {
            throw new OutOfRangeException(‘The supplied ID ‘ . $id . ‘ is invalid.’);
        }
        $this->_id = $id;
    }
     
    /**
     * Set the user’s name
     */ 
    public function setName($name)
    {
        if (strlen($name) < 2 || strlen($name) > 32) {
            throw new OutOfRangeException(‘The supplied name ‘ . $name . ‘ is invalid.’);
        }
        $this->_name = $name;
    }
     
    /**
     * Set the user’s email address
     */
    public function setEmail($email)
    {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new OutOfRangeException(‘The supplied email address ‘ . $email . ‘ is invalid.’);
        }
        $this->_email = $email;
    }
}

So far, so good. At this point, I proved that building an external iterator (in this case a simple array wrapper) in PHP is actually much easier to accomplish than one might think. The question that probably keeps your mind spinning in circles is if it’s possible to traverse any child spawned from the parent “EntityAbstract” using the previous array collection.

Actually, the answer to that question is a resounding yes! But, to dissipate your eventual doubts, in the coming section I’m going to create an example that will demonstrate how to achieve this with the earlier “User” class.

To see how this example will be developed, go ahead and keep reading.

Putting the external iterator to work: setting up a final example

Remember the script that I wrote at the beginning to test the “EntityAbstract” class when it made use of an ugly inner iterator? Well, the good news is that it’s possible to reutilize it for trying out the improved version of the class. In case that you’re skeptical about my words, take a peek at the following code fragment:

<?php

use ModelUser as User;

// include the autoloader
require_once ‘Autoloader.php';
Autoloader::getInstance();

// create a user object
$user = new User(array(
    ‘_id’    => 1,
    ‘_name’  => ‘Sandra Wilson’,
    ‘_email’ => ‘sandra@domain.com’
 ));

// count the number of fields of the user
echo ‘The number of fields of the user is ‘ . count($user) . ‘.<br />';

/* displays the following
The number of fields of the user is 3.
*/

// iterate over the user and display their value/field pairs
foreach ($user as $field => $value) {
    echo "The value assigned to the field ‘$field’ is $value.<br />";
}

/* displays the following
The value assigned to the field ‘_id’ is 1.
The value assigned to the field ‘_name’ is Sandra Wilson.
The value assigned to the field ‘_email’ is sandra@domain.com.
*/

There you have it. From a client code’s standpoint, any subclass derived from “EntityAbstract” is really traversable, something demonstrated above with the familiar “User” class. In this case, however, things are done more efficient behind the scenes, as the whole iteration process has been delegated to the outer “ArrayCollection” iterator.

Of course, you might argue that it’s possible to produce a similar result by calling directly the class’ “toArray()” method, and certainly you’d be right. However, having a class that transparently gets an outer iterator when used in a “foreach” loop can be very powerful, specially when it comes to lazy-loading data from the storage layer. But, in fact I’m getting ahead of myself, since this topic will be covered in detail in a forthcoming installment of this series. So, in the interim feel free to stop by the article’s final thoughts.  

Final thoughts

In this first chapter of the series, I introduced you to implementing external iterators in PHP. As you saw for yourself, in most cases the development of this kind of structures relies on the use of the IteratorAggregate SPL interface; so if you’re not familiar with the inner workings of this native interface, I suggest you to take a look at its documentation here (http://www.php.net/manual/en/class.iteratoraggregate.php).

In addition, it’s valid to stress that in the prior example I used a custom outer iterator to traverse the protected members of any entity derived from the abstract “AbstractEntity” class. While this approach worked decently well, it’s feasible to utilize the built-in ArrayIterator PHP class and achieve similar results.

In line with this idea, in the coming tutorial I’m going to amend the source code of the aforementioned “AbstractEntity” class, which this time will employ the native ArrayIterator to traverse concrete entities spawned from it.

Don’t miss the next part!      

 

[gp-comments width="770" linklove="off" ]

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort