Home arrow PHP arrow External PHP Iterators using the ArrayIterator Class

External PHP Iterators using the ArrayIterator Class

If you, as a PHP developer, are looking for an approach that lets you build traversable classes that stick more strictly to the Single Responsibility Principle, then maybe it is time to consider the functionality provided by external iterators.

TABLE OF CONTENTS:
  1. External PHP Iterators using the ArrayIterator Class
  2. Iterate Class Properties with ArrayCollection
By: Alejandro Gervasio
Rating: starstarstarstarstar / 1
March 31, 2011

print this article
SEARCH DEV SHED

TOOLS YOU CAN USE

advertisement

To demonstrate how useful external iterators can be, in the introductory part of this series I implemented a custom array collection, which was used by a separate class, whose main area of concern was to model generic entities. The purpose of coding this array wrapper was to traverse the protected fields of the mentioned class without having to use an inner iterator. A dead simple solution, but also effective. You can read more in my article: External Iterators in PHP Using OOP.

It’s worth noting that it is not always necessary to create custom external iterators from scratch. If the data that must be traversed is made up of plain array elements, it’s possible to use the ArrayIterator class that comes included with the SPL. Taking into account this possibility, in this second installment of the series I’ll be illustrating how to use this built-in iterator, this time to traverse the data members of the entity modeling class previously created.

Want to learn the fine details of this refactoring process? Then jump ahead and read on!

Using a Custom External Iterator: A Brief Look at a Prior Example

Before I show you how easy it is to build traversable objects using the native PHP ArrayIterator class,   it would be useful to take a quick look at the example created in the first installment of this series, which as mentioned in the introduction, demonstrated how to traverse the protected fields of an entity class via a custom external iterator.

With that being said, here’s the abstract class that models generic entities and , in turn, uses an outer array collection to iterate over the properties of any subclass derived from it:

(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 Utility\ArrayCollection($this->toArray());
    }
   
    /**
     * Count the number of elements in the external iterator
     */
    public function count()
    {
        return count($this->getIterator());
    }   
}

As you can see, aside from setting/retrieving the values assigned to the fields of an entity, the above class implements the Countable and IteratorAggregate SPL interfaces, in order to count and iterate over the fields in question via an outer array collection. If you’re wondering how this collection looks, here is its source code:

(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;
    }
}

If you are used to building wrappers around PHP native arrays, the implementation of the above “ArrayCollection” class should be quite easy to grasp for you. In simple terms, all this class does is traverse the elements added to its protected $_data array and access them via the methods declared in the ArrayAccess SPL interface.

And now that you have grasped how the previous iterator works, it’s time to spawn a concrete subclass from the parent “EntityAbstract”, so that you can see how it can be easily traversed using the pertaining iterator. In this particular case, this subclass is responsible for modeling generic users and looks like this: 

(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;
    }
}

The above “User” class’ source code speaks for itself, so move on and look at the following script, which shows how to iterate over the class’ properties using the earlier “ArrayCollection” iterator.



 
 
>>> 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: