PHP arrays are generally a very powerful object container. But still, we can easily add a little more fuel to them. Imagine an iterator object as a kind of wrapper around our arrays. What we will try to accomplish here is to create unique interface for traversing arrays and to add a little more control over how our objects are created and finally, to support lazy loading.
Interface
Iterator has a very simple and many times seen interface.
<?php
function Iterator($array) // Constructor. Takes array to be traversed as a parameter.
function reset() // Sets internal pointer to the first element
function end() // Sets internal pointer to the last element
function seek($position) // Sets internal pointer to desired position
function next() // Returns next element in the iteration
function previous() // Returns previous element in the iteration
?>
With the interface like this you can easily perform all your daily tasks
(such as traversing arrays) in any way you want and from any position
you want. One advantage of using this approach against native PHP array
functions is that you have one interface for all of your array tasks.
You will not use foreach() construct in one case, list-each combination
in other and yet next() and prev() functions in third any more. One
more advantage is that now you can easily position on any particular
element and start traversing from there (in any way you want). Here are few
code examples:
<?php
// $array = ?. // initialize the array
$iterator = new Iterator($array);
// traverse the array
while ($elem = $iterator->next()) {
echo $elem;
}
// traverse array in reverse order
$iterator->end();
while ($elem = $iterator->previous()) {
echo $elem;
}
// traverse array from fifth element on
$iterator->seek(5);
while ($elem = $iterator->next()) {
echo $elem;
}
?>
OK, you say, this is all nice but there is nothing I can't do with
combination of native PHP functions. Besides the fact that you are accessing all your
arrays through unique interface, another (and most important) advantage
is that Iterator's object structure allows you to easily expand its
functionality.
ObjectIterator interface
Often I have ended up in situations where my object methods had to return
an array of some other object as a result. Usually that object is loaded
from the database, but could be some other situation such as obtaining
objects through some RPC protocol (XML-RPC, SOAP, ...) or endless
other situation combinations that you experience every day. In this article we
will focus on the first problem and briefly explain how to empower
Iterator for the purpose you'd need.
Suppose that you are developing an address book for some large web
application. Your address book will work with companies and persons. In
addition, companies could have an endless number of employees (that are also
kinds of persons). So far we have recognized two objects in our application:
Company and Person. Also, it is clear that the company will have method
getEmployees() that returns an array of Person objects. There are a number of
possible implementations of this method. Here are some usual
implementations:
First, you could write a query to collect all the ids of all the company
employees. Then you could make an array that contains all the objects and
returns this array. This would look something like this (supposing you have a
database wrapper):
<?php
function getEmployees() {
$query = "SELECT id FROM persons WHERE companyid = $this->companyid";
$stmt = execute_query($query); // creates statement object
$this->employees = array();
while ($row = fetch_object($stmt) {
$this->employess[$row->id] = new Person($row->id); // object creation
}
return $this->employees;
}
?>
and the usage would be:
<?php
$company = new Company("Datagate");
$employees = $company->getEmployees();
foreach ($employees as $id =>$employee)
$employee->addVacationDays(3); // object usage
?>
OK, these look like fairly obvious solutions. But, it has few big flaws. All
objects are created but we don't know if we're going to use them all.
There are two performance problems here. First accessing a relational
database (for creating these objects) can be very time expensive. And if
the company has 500 employees and you need to access data for only 50, that is
lot of time wasted. Imagine now, that we are loading these objects through
RPC which is even slower. This could seriously affect application
performance. Now, even if all objects are needed we don't need them at the
same time, we need objects one by one as we are traversing the array. The
solution above is a huge waste of resources (memory and time).
The solution to these performance problems looks so obvious. Let's return
just an array of employee ids. The code would look something like this:
<?php
function getEmployees() {
$query = "SELECT id FROM persons WHERE companyid = $this->companyid";
$stmt = execute_query($query); // creates statement object
$this->employees = array();
while ($row = fetch_object($stmt) {
$this->employess[$row->id] = $row->id;
}
return $this->employees;
}
?>
and the usage would be:
<?php
$company = new Company("Datagate");
$employees = $company->getEmployees();
foreach ($employees as $id) {
$employee = new Employee($id); // object creation
$employee->addVacationDays(3); // object usage
}
?>
This looks fine at the first sight. We have saved time and memory
, but another problem has arisen. Suppose that the code for creating
Employee object changes, for example you need to add extra argument to the
constructor or some extra initialization (these are things that are
happening on real projects). In that case you'll need to modify your code in
many places (wherever you have used getEmployees() method), And that is
a problem.
The third solution is to use an ObjectIterator class that is extended from
Iterator. In this example we will see how easy it is to extend Iterator
class to serve your purposes. When you are using ObjectIterator your
getEmployee() function will stay the same as in second solution. So, we
have saved our resources. No unnecessary objects are created and
everything looks just fine. Now let's look at the usage code:
<?php
$company = new Company("Datagate");
$iterator = new Iterator($company->getEmployees(), "Employee");
while ($employee = $iterator->next()) {
$employee->addVacationDays(3);
}
?>
We see now that the object creation code is hidden in the ObjectIterator
class, so it is now easy to support changes.
ObjectIterator implementation
The code for ObjectIterator is quite simple.
<?php
/**
* Implements iterator for traversing collection of objects
* for the given array od object identifiers
*
* @version 1.0
* @author <a href=mailto:chubrilo@yahoo.com>Dejan Bosanac</a>
*/
class ObjectIterator extends Iterator {
var $_objectName;
/**
* Constructor
* Calls initialization method (@see Iterator::_initialize())
* and do some specific configuration
* @param array $array array of object ids to be traversed
* @param string $objectName class of objects to be created
* @access public
*/
function ObjectIterator($array, $objectName) {
$this->_initialize($array);
$this->_objectName = $objectName;
}
/**
* Returns object with given id
* @param Object $id identifier of the object
* @return Object next object in the collection
* @access private
*/
function _fetchObject($id) {
return new $this->_objectName($id);
}
}
?>
It has $_objectName class member that represent class of the object that has
to be returned by next() and previous() methods. The constructor sets this
internal variable and calls an initialization function (defined in Iterator class). The most important thing is the _fetchObject() function. It encapsulates code for object creation. It is called from next() and previous() methods and takes object id as a parameter. So all your object creation code is localized here, thus making it easy to change and expand.
So, here are instructions for creating our new type of iterators. First,
make your constructor (which has to have an array as a parameter) which calls
_initialize() function from Iterator class. Second, override _fetchObject
method to do whatever you have to do to make your objects. And, that
would be all.
In conclusion
Iterator will not slow down your application in a way that it will
need new hardware to run it. It has some overhead, but for that price
you get clean and easy readable code that is flexible enough for future
software enhancements.
You can download PHP Iterator from
here