Home arrow PHP arrow External Iterators in PHP Using OOP

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!

TABLE OF CONTENTS:
  1. External Iterators in PHP Using OOP
  2. Creating the External Iterator in PHP
  3. Defining the Functionality of PHP Array Iterators
By: Alejandro Gervasio
Rating: starstarstarstarstar / 4
March 21, 2011

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

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 Model\User 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. 



 
 
>>> More PHP Articles          >>> More By Alejandro Gervasio
 

blog comments powered by Disqus
escort Bursa Bursa escort Antalya eskort
   

PHP ARTICLES

- Hackers Compromise PHP Sites to Launch Attac...
- Red Hat, Zend Form OpenShift PaaS Alliance
- PHP IDE News
- BCD, Zend Extend PHP Partnership
- PHP FAQ Highlight
- PHP Creator Didn't Set Out to Create a Langu...
- PHP Trends Revealed in Zend Study
- PHP: Best Methods for Running Scheduled Jobs
- PHP Array Functions: array_change_key_case
- PHP array_combine Function
- PHP array_chunk Function
- PHP Closures as View Helpers: Lazy-Loading F...
- Using PHP Closures as View Helpers
- PHP File and Operating System Program Execut...
- PHP: Effects of Wrapping Code in Class Const...

Developer Shed Affiliates

 


Dev Shed Tutorial Topics: