Home arrow PHP arrow Page 2 - External PHP Iterators using the ArrayIterator Class

Iterate Class Properties with ArrayCollection - PHP

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

<?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 was really simple to code and read, wasn’t it? As seen in the above code fragment, traversing any object spawned from the “User” class is a breeze, thanks to the functionality provided by the custom array collection. As noted in the beginning, however, it’s possible to get similar results by using the native ArrayIterator class included with the SPL.

To elaborate this concept, in the following segment I’m going to modify the implementation of the prior “EntityAbstract” class, which will make use of the aforementioned ArrayIterator to traverse the protected data members of any subclass spawned from it.

Tweaking the Earlier Example: Using the Native ArrayIterator Class 

Modifying the previous “EntityAbstract” class so that it can use the native ArrayIterator SPL class is a straightforward process reduced to refactoring its “getIterator()” and “count()” methods and nothing else. Of course, this is pure theory that needs to be backed up with functional code, so pay close attention to the following snippet, which shows the modified version of this class:

(Model/EntityAbstract.php)

<?php

namespace Model;

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 \ArrayIterator($this->toArray());
    }

    /**
     * Count the number of elements in the external iterator
     */
    public function count()
    {
        return count($this->getIterator());
    }


  
Effectively, the implementation of the above class remains nearly the same, except for a couple of subtle details: as mentioned, the “getIterator()” and “count()” methods now use (internally) the native ArrayIterator class as an external iterator, instead of the custom array collection previously created.

   Effectively, the implementation of the above class remains nearly the same, except for a couple of subtle details: as mentioned, the “getIterator()” and “count()” methods now use (internally) the native ArrayIterator class as an external iterator, instead of the custom array collection previously created.

Since ArrayIterator on its own packages the functionality required for traversing, counting and handling plain PHP arrays, in this case it can be used for traversing the properties of any entity derived from the earlier abstract parent.

Moreover, to demonstrate the veracity of my claim, in the following segment I’ll be setting up a short script, similar to others that you learned before in this series, which will make use of the amended version of the “EntityAbstract” class, to iterate over the fields of a user object. 

Traversing User Objects: Seeing the ArrayIterator in Action

If you’re anything like me, you’ll want to see if the revamped “EntityAbstract” class is really iteratable. To satisfy your curiosity, I’m going to use the same “User” subclass that you saw in the earlier segment. As you’ll recall, this derivative is responsible for modeling users and its implementation 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;
    }
}

Admittedly, I included the above class only for the sake of completeness, but at this point you should already be familiar with its inner workings. So, it’s time to make things a bit more interesting and see if the properties of any user object can be actually traversed. 

The following script shows this in a nutshell. Check it out:

<?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 ran really smooth! Effectively, any object originated from the “User” class (and in turn derived from its abstract parent) can be placed within a “foreach” loop and traversed at will. And best of all, the whole process didn’t require us to write a custom iterator, as the native ArrayIterator class did the hard work for us.

Even when somewhat contrived, this example showed that when it comes to using external iterators, some of the SPL classes can save you from reinventing the wheel (unless you really need to have better and flashier wheels).

But wait a minute! Does this mean that the functionality provided by external iterators is reduced to traversing a few entities and nothing else? Fear not, as they can be utilized in a great variety of use cases. But to learn more on this topic, feel free to take a look at the tutorial’s closing thoughts.

Final thoughts

Over this second chapter of the series, I demonstrated with yet another approachable example, how easy is to create traversable classes by using an external iterator. In this specific case, I decided to employ the native ArrayIterator class that comes bundled with the SPL to traverse the protected fields of an entity class; but as you saw in a previous tutorial, it’s possible (and in most cases desirable) to implement a custom iterator, capable of performing additional tasks, other than the ones provided by the classic tandem composed by the Iterator/Countable/ArrayAccess SPL interfaces.

While it’s fair to admit that this isn’t an exclusive ability of external iterators, the truth is that it’s feasible to use them for lazy-loading data as well. You may be wondering how this can be done, right? Considering that an outer iterator is usually invoked by a class implementing the “IteratorAggregate” native interface, it’s really simple to load data on request, since its “getIterator()” method is invoked whenever any object of the class is used within a “foreach” construct.

Based on this concept, in the coming tutorial I’m going to create a simple file helper, which will be able to lazy-load contents from a specific text file, thanks to the “clever” use of an external iterator.

Don’t miss the next part!

 



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