PHPRO.ORG

Introduction to SPL

Introduction to SPL

Introduction to Standard PHP Library (SPL)

By Kevin Waterson

Contents

What is SPL.

SPL provides a standard set of interfaces for PHP5. The aim of SPL is to implement some efficient data access interfaces and classes for PHP. Functionally it is designed to traverse aggregate structures (anything you want to loop over). These may include arrays, database result sets, xml trees, directory listings or any list at all. Currently SPL deals with Iterators. To see all the classes available to SPL, this simple snippet will show you.

<?php
// a simple foreach() to traverse the SPL class names
foreach(spl_classes() as $key=>$value)
{
echo 
$key.' -&gt; '.$value.'<br />';
}
?>

This will provide you with a list something like this:

  • AppendIterator -> AppendIterator
  • ArrayIterator -> ArrayIterator
  • ArrayObject -> ArrayObject
  • BadFunctionCallException -> BadFunctionCallException
  • BadMethodCallException -> BadMethodCallException
  • CachingIterator -> CachingIterator
  • Countable -> Countable
  • DirectoryIterator -> DirectoryIterator
  • DomainException -> DomainException
  • EmptyIterator -> EmptyIterator
  • FilterIterator -> FilterIterator
  • InfiniteIterator -> InfiniteIterator
  • InvalidArgumentException -> InvalidArgumentException
  • IteratorIterator -> IteratorIterator
  • LengthException -> LengthException
  • LimitIterator -> LimitIterator
  • LogicException -> LogicException
  • NoRewindIterator -> NoRewindIterator
  • OuterIterator -> OuterIterator
  • OutOfBoundsException -> OutOfBoundsException
  • OutOfRangeException -> OutOfRangeException
  • OverflowException -> OverflowException
  • ParentIterator -> ParentIterator
  • RangeException -> RangeException
  • RecursiveArrayIterator -> RecursiveArrayIterator
  • RecursiveCachingIterator -> RecursiveCachingIterator
  • RecursiveDirectoryIterator -> RecursiveDirectoryIterator
  • RecursiveFilterIterator -> RecursiveFilterIterator
  • RecursiveIterator -> RecursiveIterator
  • RecursiveIteratorIterator -> RecursiveIteratorIterator
  • RecursiveRegexIterator -> RecursiveRegexIterator
  • RegexIterator -> RegexIterator
  • RuntimeException -> RuntimeException
  • SeekableIterator -> SeekableIterator
  • SimpleXMLIterator -> SimpleXMLIterator
  • SplFileInfo -> SplFileInfo
  • SplFileObject -> SplFileObject
  • SplObjectStorage -> SplObjectStorage
  • SplObserver -> SplObserver
  • SplSubject -> SplSubject
  • SplTempFileObject -> SplTempFileObject
  • UnderflowException -> UnderflowException
  • UnexpectedValueException -> UnexpectedValueException

What are iterators?

An Iterator is an object that traverses a structure eg: an array or a directory listing or possibly a set of database result sets or other resource. This is not an accurate discription, but more will become clear later by way of example. There are different types of iterators for dealing with different types of data such as array Iterators, Directory Iterators and more. Here we will begin to get familiar with them beginning with the DirectoryIterator. What is important to note is they can all be accessed with a standard interface. This means that regardless of the data type, access to the information is standardised. This is a real step forward for PHP.

Apply Callback Function to Iterators

As Iterators are read-only, a callback cannot act directly on iterator values. But this does not limit the use of a callback in being able to affect the output iterator elements. In this example, a simple array is used and a callback function applied to the each element of the iterator with the iterator_apply() method.


<?php
    
/**
     * @Capitalise First Letter
     *
     * @param Iterator $it
     *
     * @return bool
     *
     */
    
function addCapsIterator $it )
    {
        echo 
ucfirst$it->current() ) . '<br />';
        return 
true;
    }

    
/*** an array of aussies ***/
    
$array = array( 'dingo''wombat''wallaby' );

    try
    {
        
$it = new ArrayIterator$array );
        
iterator_apply$it'addCaps', array($it) );
    }
    catch(
Exception $e)
    {
        
/*** echo the error message ***/
        
echo $e->getMessage();
    }
?>

Note that the addCaps function returns bool true. If this does happen, only the first element will be affected because the iterator_apply() function will continue to execute only while the return value is bool true. The results from the script above produce results with the first letter of the array elements caplitalized.

Dingo
Wombat
Wallaby

Extending the DirectoryIterator class.

<?php
    
/*** class definition to extend Directory Iterator class ***/
    
class DirectoryReader extends DirectoryIterator
    
{
        
// constructor.. duh!
        
function __construct($path)
        {
            
/*** pass the $path off to the parent class constructor ***/
            
parent::__construct($path);
        }

        
/*** return the current filename ***/
        
function current()
        {
            return 
parent::getFileName();
        }

        
/*** members are only valid if they are a directory ***/
        
function valid()
        {
            if(
parent::valid())
            {
                if (!
parent::isDir())
                {
                    
parent::next();
                    return 
$this->valid();
                }
            return 
TRUE;
            }
            return 
FALSE;
        }

    } 
// end class

    
try
    {
        
/*** a new iterator object ***/
        
$it = new DirectoryReader('./');
        
/*** loop over the object if valid ***/
        
while($it->valid())
        {
            
/*** echo the current object member ***/
            
echo $it->current().'<br />';
            
/*** advance the internal pointer ***/
            
$it->next();
        }
    }
    catch(
Exception $e)
    {
        echo 
'No files Found!<br />';
    }

?>

The code above demonstrates how we can overload the Iterator methods to reduce the logic within the user code. This creates great opportunities to make portable classes for re-use, thus reducing user code and speeding up development time. Once again the use of Exceptions shows how easy it can be to catch errors and deal with them. We could, of course, use the isFile() method in place of isDir() to show only files. The possibilities are endless.

Lets go over what we have done here. First we have created a small class to extend the internal DirectoryIterator class. In the constructor we have passed the $path variable and called the parent class constructor. The parent class is of course the SPL DirectoryIterator class.

The valid method checks if the file meets a criteria. This begins with the parent::valid checking to see that the current iteration has a value. From there we check if the current Iteration is a directory by calling the parent::isDir() method. If this is not a directory, the iterator is advanced with theparent::next() method and checks again if the next value is valid. We could have used the FilterIterator to achieve the same goal, but more on that later.

ArrayObject

The ArrayObject allows for external traversal of arrays and to create instances of ArrayIterator. Much like the Directory iterator, we can see the methods available to the Array Iterator with a simple snippet.


<?php
foreach(get_class_methods(new ArrayObject()) as $key=>$method)
{
echo 
$key.' -> '.$method.'<br />';
}
?>

Again we see the class methods in a list as follows.

  • 0 -> __construct
  • 1 -> offsetExists
  • 2 -> offsetGet
  • 3 -> offsetSet
  • 4 -> offsetUnset
  • 5 -> append
  • 6 -> getArrayCopy
  • 7 -> count
  • 8 -> getFlags
  • 9 -> setFlags
  • 10 -> asort
  • 11 -> ksort
  • 12 -> uasort
  • 13 -> uksort
  • 14 -> natsort
  • 15 -> natcasesort
  • 16 -> getIterator
  • 17 -> exchangeArray
  • 18 -> setIteratorClass
  • 19 -> getIteratorClass

We will see in the following code snippets, various ways to use some of these methods along with the ArrayIterator class. First a simple ArrayObject and ArrayIterator.

<?php

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

/*** create the array object ***/
$arrayObj = new ArrayObject($array);

/*** iterate over the array ***/
for($iterator $arrayObj->getIterator();
 
/*** check if valid ***/
 
$iterator->valid();
 
/*** move to the next array member ***/
 
$iterator->next())
    {
    
/*** output the key and current array value ***/
    
echo $iterator->key() . ' => ' $iterator->current() . '<br />';
    }
?>

In the above script we have externally traversed the array object with getIterator(). We could have used a foreach on the array and the getInstance() method would be called implicitly. The key() and current() methods also belong to the ArrayIterator Instance. From the above code snippet we get a simple array output as follows

  • 0 => koala
  • 1 => kangaroo
  • 2 => wombat
  • 3 => wallaby
  • 4 => emu
  • 5 => kiwi
  • 6 => kookaburra
  • 7 => platypus

You might be getting the swing of this by now, so to keep it simple we will use the same code from above and append, or add, a value to the array and iterate over it. Note that the ArrayObject::getIterator returns an ArrayIterator instance working on the original ArrayObject instance. It's only method getIterator() is automatically called in iteration, thus if you put an ArrayObject which implements that interface into a foreach() construct that method is being executed automatically. Since the ArrayObject returns an ArrayIterator interface the foreach() Construct will reset that iterator and work on it.


<?php

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

/*** create the array object ***/
$arrayObj = new ArrayObject($array);

/*** append a value to the array ***/
$arrayObj->append('dingo');

/*** iterate over the array ***/
for($iterator $arrayObj->getIterator();
 
/*** check if valid ***/
 
$iterator->valid();
 
/*** move to the next array member ***/
 
$iterator->next())
{
/*** output the key and current array value ***/
echo $iterator->key() . ' -> ' $iterator->current() . '<br />';
}
?>

Now we see that the list has the value 'dingo' on the end of it and we begin to see the usefulness of an Object Oriented approach. Our list looks like this.
0 -> koala
1 -> kangaroo
2 -> wombat
3 -> wallaby
4 -> emu
5 -> kiwi
6 -> kookaburra
7 -> platypus
8 -> dingo

We can sort the array with one of the array sort methods available to the array object. In this instance, we will sort alphabetically using the natcasesort() method.

<?php

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

/*** create the array object ***/
$arrayObj = new ArrayObject($array);

/*** sort alphabetically ***/
$arrayObj->natcasesort();

/*** iterate over the array ***/
for($iterator $arrayObj->getIterator();
 
/*** check if valid ***/
 
$iterator->valid();
 
/*** move to the next array member ***/
 
$iterator->next())
{
/*** output the key and current array value ***/
echo $iterator->key() . ' -> ' $iterator->current() . '<br />';
}
?>

Now we see our array has changed and the output now looks like this:
4 -> emu
1 -> kangaroo
5 -> kiwi
0 -> koala
6 -> kookaburra
7 -> platypus
3 -> wallaby
2 -> wombat
As you can see, the order has changed but not the keys. We could sort them similarly with asort, usort or any of the other sorting methods listed by get_class_methods(). To see a count of the total number of members in our array object is a trivial matter of using the count() method as shown below.

<?php

/*** a simple array ***/
$array = array(\koala\, \kangaroo\, \wombat\, \wallaby\, \emu\, \kiwi\, \kookaburra\, \platypus\);

/*** create the array object ***/
$arrayObj = new ArrayObject($array);

/*** echo the total number of elements ***/
echo $arrayObj->count();
?>

We see the return value from the count() method is 8. We may also note that one of the beasts in our array of animals does not belong. This is an array of animals native to Australia (straya), but the kiwi is a New Zealand critter. To remove this undesirable from the array we use theoffsetUnset() method. Lets see how it works.

<?php

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

/*** create the array object ***/
$arrayObj = new ArrayObject($array);
/*** unset the array member ***/
$arrayObj->offsetUnset(5);

/*** loop of the array object ***/
for($iterator $arrayObj->getIterator();
 
/*** check if valid ***/
 
$iterator->valid();
 
/*** move to the next array member ***/
 
$iterator->next())
{
/*** output the key and current array value ***/
echo $iterator->key() . ' -> ' $iterator->current() . '<br />';
}
?>

The above code will produce this output..
0 -> koala
1 -> kangaroo
2 -> wombat
3 -> wallaby
4 -> emu
6 -> kookaburra
7 -> platypus
The offending New Zealand critter has been removed from the array but the array is not re-indexed.

To check for the existance of an offset within the array, we use the offsetExists() method as demonstrated below.

<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

/*** create the array object ***/
$arrayObj = new ArrayObject($array);

 if (
$arrayObj->offsetExists(3))
{
/*** unset the key ***/
echo 'Offset Exists</br />';
}
?>

The above snippet tells us the offset Exists and runs the code in the if{} block. This has the same functionality as array_key_exists(). It is possible to change the value of an array member with the offsetSet method. Here we show how by changing the kiwi to the Australian native bird 'galah'.

<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create the array object ***/
$arrayObj = new ArrayObject($array);

/*** set the offset of 5 to a new value ***/
$arrayObj->offsetSet(5"galah");

/*** loop of the array object ***/
for($iterator $arrayObj->getIterator();
/*** check if valid ***/
$iterator->valid();
/*** move to the next array member ***/
$iterator->next())
{
/*** output the key and current array value ***/
echo $iterator->key() . ' -> ' $iterator->current() . '<br />';
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

We see that when we iterate over the array object the kiwi has been set to 'galah'. We can also get a value from the array object by using the offsetGet() method.

<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create the array object ***/
$arrayObj = new ArrayObject($array);

/*** echo the value of the array object 4 ***/
echo $arrayObj->offsetGet(4);
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

The script above prints 'emu' as this is the value of the offset 4.

There are times you may need a copy of an array to edit or to do comparisons, to this end the method getArrayCopy() is provided for just that purpose. Here we show a simple array copy.

<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create the array object ***/
$arrayObj = new ArrayObject($array);

/*** create a copy of the array object ***/
$arrayObjCopy $arrayObj->getArrayCopy();

/*** iterate over the array copy ***/
for($iterator $arrayObjCopy->getIterator();
/*** check if valid ***/
$iterator->valid();
/*** move to the next array member ***/
$iterator->next())
{
/*** output the key and current array value ***/
echo $iterator->key() . ' => ' $iterator->current() . '<br />';
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

When you run the above code you will get an error message similar to this..
Fatal error: Call to a member function getIterator() on a non-object in /www/spl.php on line 13
This is because the copy of the array is just a copy of the array, it is NOT an array object that SPL can iterate over, so we must create a new array object as shown here:

<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create the array object ***/
$arrayObj = new ArrayObject($array);

/*** create a copy of the array object 
you MUST create a new array object also ***/
$arrayObjCopy = new ArrayObject($arrayObj->getArrayCopy());

/*** iterate over the array copy ***/
for($iterator $arrayObjCopy->getIterator();
/*** check if valid ***/
$iterator->valid();
/*** move to the next array member ***/
$iterator->next())
{
/*** output the key and current array value ***/
echo $iterator->key() . ' -> ' $iterator->current() . '<br />';
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

Now of course, we can safely iterate over the newly create array and array object, the results will be as before:
0 -> koala
1 -> kangaroo
2 -> wombat
3 -> wallaby
4 -> emu
5 -> kiwi
6 -> kookaburra
7 -> platypus

The ArrayObject class also comes with several flags..

  • ARRAY_AS_PROPS
  • STD_PROP_LIST

These flags are quite funky the ARRAY_AS_PROPS constant allows array indexes to be accessed as properties in read/write, while the STD_PROP_LIST allows properties of the object to have their normal functionality. Lets begin with a short demonstration of ARRAY_AS_PROPS.

<?php

?>

ArrayIterator

If you have not read the previous section on ArrayObject it is highly recommended you do so, as this section is really an addition to the previous where we have been using the ArrayIterator to traverse ArrayObjects.. The ArrayIterator makes use of the ArrayObject to traverse arrays and an understanding of this is important. Much like the Directory iterator, we can see the methods available to the Array Iterator with a simple snippet.

<?php

foreach(get_class_methods(new ArrayIterator()) as $key=>$method)
{
echo 
$key.' -> '.$method.'<br />';
}
?>

The above snippet will produce a list of methods available to the ArrayIterator class as shown below.

  • 0 -> __construct
  • 1 -> offsetExists
  • 2 -> offsetGet
  • 3 -> offsetSet
  • 4 -> offsetUnset
  • 5 -> append
  • 6 -> getArrayCopy
  • 7 -> count
  • 8 -> getFlags
  • 9 -> setFlags
  • 10 -> asort
  • 11 -> ksort
  • 12 -> uasort
  • 13 -> uksort
  • 14 -> natsort
  • 15 -> natcasesort
  • 16 -> rewind
  • 17 -> current
  • 18 -> key
  • 19 -> next
  • 20 -> valid
  • 21 -> seek

Some of this should look familiar if you have read the DirectoryIterator section. Remember the 'S' in SPL is for 'Standard' and here we see why. From here, using the ArrayIterator is quite simple given any array. Here we iterate over a simple array and output the variables to the browser. The ArrayIterator takes an ArrayObject as its arguement, so, before we can use the ArrayIterator class we must create the ArrayObject to be able to iterate over it.

We have already seen the ArrayIterator in action with the ArrayObject. Lets see a simple array iteration.


<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create a new object ***/
$object = new ArrayIterator($array);
/*** rewind to the beginning of the array ***/
$object->rewind();
/*** check for valid member ***/
while($object->valid())
{
/*** echo the key and current value ***/
echo $object->key().' -&gt; '.$object->current().'<br />';
/*** hop to the next array member ***/
$object->next();
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

Of course, the script above iterates over the array and produces a list of the array keys and values, it shows how we have manually called the ArrayIterator to traverse the array of animals. We could however, call the ArrayIterator implicitly like this:


<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
$object = new ArrayIterator($array);
foreach(
$object as $key=>$value)
{
echo 
$key.' => '.$value.'<br />';
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

Above we see the ArrayIterator is implicitly used but we the difference is hidden from us. A traditional array traversal allocates every member of the array to memory. The iterator assigns memory for the current element only. This can be a huge benifit when dealing with large arrays.

Array offsets may be checked for and acquired using the offSetExists() and offSetGet() methods as shown below.


<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
$object = new ArrayIterator($array);
if(
$object->offSetExists(2))
{
echo 
$object->offSetGet(2);
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

The above code checks for the existence of the offset 2 with the offSetExists() method. When it is found, the value of the offset is used to display it using offSetGet(). We can also set and unset array members within the array.


<ul>
<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
$object = new ArrayIterator($array);
/*** check for the existence of the offset 2 ***/
if($object->offSetExists(2))
{
/*** set the offset of 2 to a new value ***/
$object->offSetSet(2'Goanna');
}
 
/*** unset the kiwi ***/
 
foreach($object as $key=>$value)
{
/*** check the value of the key ***/
if($object->offSetGet($key) === 'kiwi')
{
/*** unset the current key ***/
$object->offSetUnset($key);
}
echo 
'<li>'.$key.' - '.$value.'</li>'."\n";
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>
</ul>

Now we see some interesting behaviour along with our expected behaviour. Using the offsetSet() method the value at the offset of 2 has been changed from wombat to Goanna. The a simple foreach to iterate over the array. The output looks like this..

  • 0 - koala
  • 1 - kangaroo
  • 2 - Goanna
  • 3 - wallaby
  • 4 - emu
  • 5 - kiwi
  • 1 - kangaroo
  • 2 - Goanna
  • 3 - wallaby
  • 4 - emu
  • 6 - kookaburra
  • 7 - platypus

Notice here how the iterator pointer has been re-wound back to the beginning of the array when we used the offsetUnset() method to unset the array whose key had the value of kiwi. The iteration begins again and as you can see, the key of 5 with the value of kiwi is missing from the object. The internal point can be re-wound intentionally should we wish, using the rewind() method.


<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create a new iterator object ***/
$object = new ArrayIterator($array);
/*** iterate over the array ***/
foreach($object as $key=>$value)
{
echo 
$value.'<br />';
}
/*** rewind the internal pointer ***/
$object->rewind();
/*** echo the current array member ***/
echo $object->current();
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

The code above will produce a list like this:

koala
kangaroo
wombat
wallaby
emu
kiwi
kookaburra
platypus
koala

The iterator has traversed the array successfully, then we have used the $object->rewind method to rewind the internal pointer back to the beginning of the array and display the value of the current member, which is koala.

If we wanted to get the size of the object, it is a trivial matter of using the count method as follows.


<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create a new iterator object ***/
$object = new ArrayIterator($array);
/*** get the size of the object ***/
echo $object->count();
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

The above will return a value of 8 as there are 8 members in our array object.

Recursive Array Iterator

The array iterator above is great for traversing single dimensional arrays. Of course not all arrays are like this, quite often we are presented with a multi-dimensional array. In the bad old days to get around this we might have used 2 foreach() loops, which would have been very costly. To traverse a multi-dimensional array we use the RecursiveArrayIterator(). Lets take a quick at how it looks.

<?php

/*** an array of animal ***/
$animals = array(
array(
'type'=>'dog''name'=>'butch''sex'=>'m''breed'=>'boxer'),
array(
'type'=>'dog''name'=>'fido''sex'=>'m''breed'=>'doberman'),
array(
'type'=>'dog''name'=>'girly','sex'=>'f''breed'=>'poodle'),
array(
'type'=>'cat''name'=>'tiddles','sex'=>'m''breed'=>'ragdoll'),
array(
'type'=>'cat''name'=>'tiddles','sex'=>'f''breed'=>'manx'),
array(
'type'=>'cat''name'=>'tiddles','sex'=>'m''breed'=>'maine coon'),
array(
'type'=>'horse''name'=>'ed','sex'=>'m''breed'=>'clydesdale'),
array(
'type'=>'perl_coder''name'=>'shadda','sex'=>'none''breed'=>'mongrel'),
array(
'type'=>'duck''name'=>'galapogus','sex'=>'m''breed'=>'pekin')
);

/*** create a new recursive array iterator ***/
$iterator =new RecursiveArrayIterator(new ArrayObject($animals));
/*** traverse the $iterator object ***/
while($iterator->valid())
{
echo 
$iterator->key().' -- '.$iterator->current().'<br/>';
$iterator->next();
}
?>

The output from the above code will produce the following..

  • 0 -- Array
  • 1 -- Array
  • 2 -- Array

You might be saying WTF! about now as all we see is an array of arrays. This is because we need to iterate recursively over the recursive iterator object. To do this we need an iterator, that will recursively iterate over an iterator. The RecursiveIteratorIterator is the correct tool for this. Lets adjust our code a little to see how it works.

<?php
$array 
= array(
array(
'name'=>'butch''sex'=>'m''breed'=>'boxer'),
array(
'name'=>'fido''sex'=>'m''breed'=>'doberman'),
array(
'name'=>'girly','sex'=>'f''breed'=>'poodle')
);

foreach(new 
RecursiveIteratorIterator(new RecursiveArrayIterator($array)) as $key=>$value)
{
echo 
$key.' -- '.$value.'<br />';
}
?>

In the above code, the RecursiveIteratorIterator() takes an iterator as its argument, in this instance, a RecursiveArrayIterator(), which in turn takes a multi-dimensional array as its arguement. This is a far more graceful way of traversing a multi-dimensional array than we have previously been used to. The above code will now recursively iterate over the multi-dimensional array, and give us output like this below.

  • name -- butch
  • sex -- m
  • breed -- boxer
  • name -- fido
  • sex -- m
  • breed -- doberman
  • name -- girly
  • sex -- f
  • breed -- poodle

Something useful.

We have seen a few implementations of iterators above,but other than iterating over an aggregate structure, such as an array, what can we do with them? The power of iterators comes in that they will iterate over _any_ aggregate structure or list as mentioned above. Here we will use PDO and SPL to create a HTML table fromdatabase results. For those not familiar with PDO it is highly recommended you get aquainted with this database interface. An Introduction to PDO is available to get you started. Lets start with the database, we will use SQLite for this excersize. A SQLite database file is availble with a periodic table of elements that will be used in this tutorial. We will see in the three examples below an increasing degree of complexity when using iterators. Each level of complexity allows better control or extensibility. To start with, we will use

<?php
// check for errors 
error_reporting(E_ALL);

 try {
$dsn = new PDO("sqlite2:/home/kevin/html/periodic.sdb");

// the result only implements Traversable
$stmt $dsn->prepare('SELECT * FROM periodic ORDER BY atomicnumber');

// exceute the query
$stmt->execute();

// the result should be an instance of PDOStatement
// IteratorIterator converts it and after that you can do any iterator operation with it
// The iterator will fetch the results for us.
$it = new IteratorIterator($stmt);

// the iterator object now has 5 arrays within.
// Each array contains a result set from the db query
foreach($it as $row)
{
// create the array object
$arrayObj = new ArrayObject($row);
// iterate over the array
for($iterator $arrayObj->getIterator();
// check if valid
$iterator->valid();
// move to the next array member
$iterator->next())
{
// output the key and current array value
echo $iterator->current() . '<br />';
}
echo 
'<hr />';
}
 
$dsn null;

catch (
PDOException $e)
{
print 
"Error!: " $e->getMessage() . "<br />";
}
?> 


The above code uses PDO to access the SQLite database, the SQL statement is then "prepared" and executed. Then SPL takes over to deal with the fetching of information from the executed query. Again we see here the ArrayObject::getIterator returns an ArrayIterator() working on the original ArrayOject instance. It's only method getIterator() is automatically called in iteration, thus if you put an ArrayObject which implements that interface into a foreach() construct that method is being executed automatically. Since the ArrayObject returns an ArrayIterator interface the foreach() Construct will reset that iterator and work on it.

$it = new IteratorIterator($stmt);


Using the IteratorIterator we can take a PDO instance and convert it allowing us to use any of the iterator functions we have seen in this tutorial, we have access to all the various iterators and methods that SPL has to offer. Lets continue through the code. The next block of code is a simple foreach() as we have seen previously on this page. Within this loop we create an Array Object from each row. Each row is an array containing our individual result rows. As seen previouly, traversing an array structure is quite simple with SPL iterators, and this is no different. A for loop is used to iterate over the array and echo the results to the broswers. No table formatting has been added at this time, simple a list of elements from the periodic table and various information about them. Each result row is seperated by an HTML horizontal line. Here we show the first two (for brevity) results of the 109 elements.

atomicnumber -> 1
0 -> 1
latin -> HYDROGENIUM
1 -> HYDROGENIUM
english -> Hydrogen
2 -> Hydrogen
abbr -> H
3 -> H

atomicnumber -> 2
0 -> 2
latin -> HELIUM
1 -> HELIUM
english -> Helium
2 -> Helium
abbr -> He
3 -> He


As you can see, we have duplicate results with both associative and numerical indexes. This is because the default behavior of the iterator is to use FETCH_BOTH. This behaviour can be changed to allow us to fetch either a numerical or an associative array with the PDOStatenem::setFetchMode. In the following example will will use this to change this behavior.
We also see in the above code that we create an Array Object each time we loop through the result set. This seems a little inefficient and we will also optimise this further in the code below.


<?php

// make it or break it 
error_reporting(E_ALL);

 try {
$dsn = new PDO("sqlite2:/home/kevin/html/periodic.sdb");

// the result only implements Traversable
$stmt $dsn->prepare("SELECT * FROM periodic ORDER BY atomicnumber");

// exceute the query
$stmt->execute();

// by setting the FETCH mode we can set the resulting arrays to numerical or associative
$result $stmt->setFetchMode(PDO::FETCH_ASSOC);

// the result should be an instance of PDOStatement
// IteratorIterator converts it and after that you can do any iterator operation with it
// The iterator will fetch the results for us.
$it = new IteratorIterator($stmt);

// Each array contains a result set from the db query
foreach($it as $row)
{
echo 
'<table style="border: solid 1px black; width: 300px;">';
// iterate over the array with the ArrayIterator
foreach(new ArrayIterator($row) as $key => $val)
{
echo 
'<tr><td style="width: 150px">'.$key.'</td><td>'.$val.'</td></tr>';
}
echo 
'</table>';
}

// reset dsn 
$dsn null;

catch (
PDOException $e)
{
print 
"Error!: " $e->getMessage() . "<br />";
}
?> 


Now we see some changes to both our code and our output. We have added some HTML formatting to better present our results data in the form of tables. We also see we have change to FETCH_ASSOC and our array indexes now derive there names from the column name in the database. More importantly, is the way in which we have simplified our code by not creating the Array Object in each iteration. Instead we directly access the the array by using this line:


 foreach(new ArrayIterator($row) as $key => $val)

Overloading

This increases our complexity level yet lessens our code structure and speeds us up as we no longer need to create the Array Object each time. By directly interfacing with the ArrayIterator we get a much smoother code flow and make better use of PHP's Object Oriented capabilities. We can take this yet a step further and overload or extend the Iterators as seen previously. Our final example looks like this:


<tablestyle="border: solid 1px black; width: 400px;">
<tr><td>Atomic Number</td><td>Latin</td><td>English</td><td>Abbr</td></tr>
<?php
// make sure its broken
error_reporting(E_ALL);

// extend the RecursiveIteratorIterator
class TableRows extends RecursiveIteratorIterator{
function 
__construct($it){
// here we use the parent class and use LEAVES_ONLY to
parent::__construct($itself::LEAVES_ONLY);


function 
beginChildren(){
echo 
'<tr>';
}

function 
endChildren() { 
echo 
'</tr>'."\n";
}

// end class

 
try {
$dsn = new PDO("sqlite2:/home/kevin/html/periodic.sdb");

// the result only implements Traversable
$stmt $dsn->prepare('SELECT * FROM periodic');

// exceute the query
$stmt->execute();

// by setting the FETCH mode we can set the resulting arrays to numerical or associative
$result $stmt->setFetchMode(PDO::FETCH_ASSOC);

// the result should be an instance of PDOStatement
// IteratorIterator converts it and after that you can do any iterator operation with it
// The iterator will fetch the results for us.

foreach(new TableRows(new RecursiveArrayIterator($stmt->fetchAll())) as $k=>$v)
{
echo 
'<td style="width: 150px; border: 1px solid black;">'.$v.'</td>';
}

$dsn null;

catch (
PDOException $e)
{
print 
"Error!: " $e->getMessage() . '<br />';
}
?> 
</table>


Here we have changed the format a little by changing the table structure a little and adding a title to it. But look at the implementation. A class has been created with the name TableRows. This is to add the HTML tags for our table rows. This is done by overloading the RecursiveIteratorIterator. class. SPL allows us to directly interface with the parent class and define actions within the parent class methods. In our example here we have passed the iterator to the constructor and then as it is internally travesed, the functions beginChildren() and endChildren() add the table row tags.

Filter Iterator

By now you should be getting in the swing of iterators. The FilterIterator is and abstract class and is the same as other iterators and we can also overload the FilterIterator class by extending it. This allows us to filter out any unwanted data from our aggregate structure, in this case an array of elements from the periodic table. First, lets look at the available methods to the FilterIterator. The FilterIterator is perhaps the easiest of all iterators to use. Simply by calling the accept method and specifying your filter requirements there.


<?php 
/*** list all class methods ***/
foreach( get_class_methods(FilterIterator) as $methodName)
{
echo 
$methodName.'<br />';
}
?>

The snippet above will produce a list of methods in the FilterIterator like this:

  • __construct
  • rewind
  • valid
  • key
  • current
  • next
  • getInnerIterator
  • accept
This list should look rather familiar by now as we see it is basically a simple iterator with a few only a fewextra methods. The method we are most concerned with here is the accept() method. By implementing the accept method we can filter out all but the information we want, or do not want. We can even make changes to data within the accept method. Here we will extend the FilterIterator class and use the accept method to cull a few kiwis from an array of Australian natives.


<?php
/*** a simple array ***/
$animals = array('koala''kangaroo''wombat''wallaby''emu''NZ'=>'kiwi''kookaburra''platypus');

class 
CullingIterator extends FilterIterator{

/*** The filteriterator takesa iterator as param: ***/
public function __constructIterator $it ){
parent::__construct$it );
}

/*** check if key is numeric ***/
function accept(){
return 
is_numeric($this->key());
}

}
/*** end of class ***/
$cull = new CullingIterator(new ArrayIterator($animals));

foreach(
$cull as $key=>$value)
{
echo 
$key.' == '.$value.'<br />';
}
?>

The above code will produce a list as follows:

  • 0 == koala
  • 1 == kangaroo
  • 2 == wombat
  • 3 == wallaby
  • 4 == emu
  • 5 == kookaburra
  • 6 == platypus

As we iterate over the array the accept() method is called to see wether the current key is valid, in this case, if it is numeric as checked with the PHP is_numeric() function. should be accepted. Of course we could do other tricks within the accept method as seen here as we filter out the prime numbers from an array of numbers.


<?php

class PrimeFilter extends FilterIterator{

/*** The filteriterator takesa iterator as param: ***/
public function __construct(Iterator $it){
parent::__construct($it);
}

/*** check if current value is prime ***/
function accept(){
if(
$this->current() % != 1)
{
return 
false;
}
$d 3;
$x sqrt($this->current());
while (
$this->current() % $d != && $d $x)
{
$d += 2;
}
 return ((
$this->current() % $d == && $this->current() != $d) * 1) == true false;
}

}
/*** end of class ***/

/*** an array of numbers ***/
$numbers range(212345,212456);

/*** create a new FilterIterator object ***/
$primes = new primeFilter(new ArrayIterator($numbers));

foreach(
$primes as $value)
{
echo 
$value.' is prime.<br />';
}
?>

From the above code we get a list of the prime numbers between 212345 and 212456 as shown here:

  • 212353 is prime.
  • 212369 is prime.
  • 212383 is prime.
  • 212411 is prime.
  • 212419 is prime.
  • 212423 is prime.
  • 212437 is prime.
  • 212447 is prime.
  • 212453 is prime.

This sort of filtering makes the FilterIterator quite a useful tool for aggregate stuctures. Remember, the data could be XML or database results and with the FilterIterator you can specify whatever rules you like in the accept() method. The job of the accept() method is to decide whether an element of the inner iterator should be accessible through the Filteriterator. The inner iterator in the case above is the ArrayIterator.

Oh, and if you think you have a better prime number checking algorithm, please submit it and we will include it.

Simple XML Iterator

The SimpleXMLIterator is, as the name suggests, quite simple in its implementation. This iterator takes a well formedxml structure (document) and can be traversed as with any aggregate structure. The SimpleXMLIterator extends SimpleXMLElement and is a recursive iterator. Lets begin with looking at the available class methods to the SimpleXMLIterator.


<?php
/*** list all class methods ***/
foreach( get_class_methods(SimpleXMLIterator) as $methodName)
{
echo 
$methodName.'<br />';
}
?>

The above code will produce a list of class melthods like this:

  • valid
  • current
  • key
  • next
  • hasChildren
  • getChildren
  • count
  • __construct
  • asXML
  • saveXML
  • xpath
  • registerXPathNamespace
  • attributes
  • children
  • getNamespaces
  • getDocNamespaces
  • getName
  • addChild
  • addAttribute

To get an idea of how the SimpleXmlIterator is implemented a few scripts are provided, however, for the sake of sanity, only the first script will contain the XML tree for use in the other scripts.


<?php

/*** a simple xml tree ***/
 
$xmlstring = <<<XML
<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<document>
<animal>
<category id="26">
<species>Phascolarctidae</species>
<type>koala</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="27">
<species>macropod</species>
<type>kangaroo</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="28">
<species>diprotodon</species>
<type>wombat</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="31">
<species>macropod</species>
<type>wallaby</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="21">
<species>dromaius</species>
<type>emu</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="22">
<species>Apteryx</species>
<type>kiwi</type>
<name>Troy</name>
</category>
</animal>
<animal>
<category id="23">
<species>kingfisher</species>
<type>kookaburra</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="48">
<species>monotremes</species>
<type>platypus</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="4">
<species>arachnid</species>
<type>funnel web</type>
<name>Bruce</name>
<legs>8</legs>
</category>
</animal>
</document>
XML;

/*** a new simpleXML iterator object ***/
try{
 
/*** a new simple xml iterator ***/
 
$it simplexml_load_string($xmlstring'SimpleXMLIterator');
 
/*** a new limitIterator object ***/
 
foreach(new RecursiveIteratorIterator($it1) as $name => $data)
{
echo 
$name.' -- '.$data.'<br />';
}
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

The above code will produce a dump of all the elements within the XML tree and if they have a value, that will also be shown. What we have done is used the simplexml_load_stirng() function, which returns an object, to load its return value directly into the SimpoeXmlIterator. Remember that the SimpleXmlIterator is a recursive iterator, so we need an iterator to iterate over a RecursiveIterator. The correct tool for this is the RecursiveIteratorIterator which will, as the name suggests, iterate over a recursive iterator. But, we usually need a little more control than that. Here we will select the species node of each animal. Only the code is shown, not the XML.


<?php
try {
/*** a new simpleXML iterator object ***/
$sxi =new SimpleXMLIterator($xmlstring);

foreach ( 
$sxi as $node )
{
foreach(
$node as $k=>$v)
{
echo 
$v->species.'<br />';
}
}
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

So now we simply have a list like this:

  • Phascolarctidae
  • macropod
  • diprotodon
  • macropod
  • dromaius
  • Apteryx
  • kingfisher
  • monotremes
  • arachnid

What has been done in the above script is to instantiate a new SimpleXmlIterator object. Then we have needed two foreach loops to correctly return the species values. This to me looks rather cumbersome, so lets try another way.


<?php

try {
$sxe simplexml_load_string($xmlstring'SimpleXMLIterator');

for (
$sxe->rewind(); $sxe->valid(); $sxe->next())
{
if(
$sxe->hasChildren())
{
foreach(
$sxe->getChildren() as $element=>$value)
{
echo 
$value->species.'<br />';
}
}
 }
 }
catch(
Exception $e)
 {
 echo 
$e->getMessage();
 }
?>

In the above code we have loaded the SimpleXmlIterator the simplexml_load_string() function and still have needed two loops, a for and a foreach, to iterate over the object. Here we have also used a conditional statement with SimpleXmlIterator::hasChildren() to check for the existence of child nodes. If a child node is found, the SimpleXmlIterator::getChildren() method can the node values and we can then view the results. Is this a better way? Is it faster? Lets look at a quick test with AB (Apache Bench) to see what results we can get.

The first method using two foreach statements and AB returned these results with 10,000 requests with a concurrency of 20.


Concurrency Level:20
Time taken for tests: 55.785945 seconds
Complete requests:10000
Failed requests:0
Write errors: 0
Total transferred:3600720 bytes
HTML transferred: 1380276 bytes
Requests per second:179.26 [#/sec] (mean)
Time per request: 111.572 [ms] (mean)
Time per request: 5.579 [ms] (mean, across all concurrent requests)
Transfer rate:63.03 [Kbytes/sec] receive

The second method with 10,000 requests a concurrency of 20 produced the following results.


Concurrency Level:20
Time taken for tests: 60.696794 seconds
Complete requests:10000
Failed requests:0
Write errors: 0
Total transferred:3600000 bytes
HTML transferred: 1380000 bytes
Requests per second:164.75 [#/sec] (mean)
Time per request: 121.394 [ms] (mean)
Time per request: 6.070 [ms] (mean, across all concurrent requests)
Transfer rate:57.91 [Kbytes/sec] received

The second method comes in at about 5 seconds slower than the first. Showing that while the nested foreach loops may look a little clumsy, but as the iterator uses less memory, it is more efficient. The reality is that both these methods suffer and a better way is to had with xpath.

The SimpleXmlIterator::xpath() method allows direct iteration over an XML tree by specifying the xpath to the node. Similar to a file system path, the xpath is of the species node in our XML document looks like this:


animal/category/species

The ability to specify the xpath to the particular node saves us many iterations of the XML tree which, in turn, saves us CPU cycles, thus allowing for much faster scripts. The code below will give the same listing of species as the two examples above, but this time we will use the xpath to go directly to the node we want to view. Once again, the XML tree has been omitted for the sake of brevity.


<?php
try {
/*** a new simpleXML iterator object ***/
$sxi =new SimpleXMLIterator($xmlstring);

/*** set the xpath ***/
$foo $sxi->xpath('animal/category/species');

/*** iterate over the xpath ***/
foreach ($foo as $k=>$v)
{
echo 
$v.'<br />';
}
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

Once again we produce a list of species from XML tree like this:

  • Phascolarctidae
  • macropod
  • diprotodon
  • macropod
  • dromaius
  • Apteryx
  • kingfisher
  • monotremes
  • arachnid

This is a far more efficient method of retrieving XML data. By simply changing the species path to name we could fetch a list of the animal names. All Australian animals would seem to be called Bruce. We can traverse unlimitted depths with the xpath. Below is smail XML tree with more depth.


<?php

/*** The path to enlightenment ***/
 
$xmlstring = <<<XML
<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<document>
<ignorance>
I dont want to know
<discovery>
Whats this then?
<awareness>
This could be useful
<acceptance>
I must use this
<enlightenment>
SPL
</enlightenment>
</acceptance>
</awareness>
</discovery>
</ignorance>

<ignorance>
knowledge is truth
<discovery>
Seek only the strong
<awareness>
look deeply for insight
<acceptance>
Open your mind
<enlightenment>
 Wisdom is yours 
</enlightenment>
</acceptance>
</awareness>
</discovery>
</ignorance>
</document>
XML;

try {
/*** a new simpleXML iterator object ***/
$sxi =new SimpleXMLIterator($xmlstring);

/*** set the xpath ***/
$foo $sxi->xpath('ignorance/discovery/awareness/acceptance/enlightenment');

/*** iterate over the xpath ***/
foreach ($foo as $k=>$v)
{
echo 
$v.'<br />';
}
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

The XML tree above has five levels from the document root. Using xpath we have traversed the tree directly to the node we seek. The saving in iterations here is obvious as we no longer need to set up a foreach heirachy to traverse the tree on the. The xpath to enlightenment just got easier.

A problem arises in XML when two documents may have similar names. If we had the xml document of animals, and another of animal owners, a conflict may arise in the name node. Each of the animals, and owners would have a name node and a method is required to distinguish between them. The SimpleXMLIterator provides several methods for the management of namespaces. These include as listed in the top of the this chapter. The script below shows a small change to the xml string that has been used and demonstrates the use of namespaces and how to utilize them within the SimpleXMLIterator.


<?php

/*** a simple xml tree ***/
 
$xmlstring = <<<XML
<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<document xmlns:spec="http://example.org/animal-species">
<animal>
<category id="26">
<species>Phascolarctidae</species>
<spec:name>Speed Hump</spec:name>
<type>koala</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="27">
<species>macropod</species>
<spec:name>Boonga</spec:name>
<type>kangaroo</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="28">
<species>diprotodon</species>
<spec:name>pot holer</spec:name>
<type>wombat</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="31">
<species>macropod</species>
<spec:name>Target</spec:name>
<type>wallaby</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="21">
<species>dromaius</species>
<spec:name>Road Runner</spec:name>
<type>emu</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="22">
<species>Apteryx</species>
<spec:name>Football</spec:name>
<type>kiwi</type>
<name>Troy</name>
</category>
</animal>
<animal>
<category id="23">
<species>kingfisher</species>
<spec:name>snaker</spec:name>
<type>kookaburra</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="48">
<species>monotremes</species>
<spec:name>Swamp Rat</spec:name>
<type>platypus</type>
<name>Bruce</name>
</category>
</animal>
<animal>
<category id="4">
<species>arachnid</species>
<spec:name>Killer</spec:name>
<type>funnel web</type>
<name>Bruce</name>
<legs>8</legs>
</category>
</animal>
</document>
XML;

/*** a new simpleXML iterator object ***/
try {
/*** a new simpleXML iterator object ***/
$sxi =new SimpleXMLIterator($xmlstring);

$sxi-> registerXPathNamespace('spec''http://www.exampe.org/species-title');

/*** set the xpath ***/
$result $sxi->xpath('//spec:name');

/*** get all declared namespaces ***/
 
foreach($sxi->getDocNamespaces('animal') as $ns)
{
echo 
$ns.'<br />';
}

/*** iterate over the xpath ***/
foreach ($result as $k=>$v)
{
echo 
$v.'<br />';
}
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

In the script above we have declared a namespace in our root as
<document xmlns:spec="http://example.org/animal-species">
Also not the addtion to the XML of a second name node. This is prefixed by the spec: prefix to identify the namespace and not confuse it with the regular animal names. The use of the SimpleXMLIterator::registerXPathNamespace() method ensures the document namespace prefixing is sane. To get the namespaces the SimpleXMLIterator::getDocNamespaces() method is used to get the names of all namespaces declared within the document. The SimpleXMLIterator::getNamespaces() method could also have been used. The SimpleXMLIterator::getDocNamespaces() method only gets those namespaces that are actually declared within the document itself.

To add a child to the XML tree is also simple with the SimpleXmlIterator::addChild method. The XML string for this script has been simplified to better demonstrate adding a child.


<?php 
 $xmlstring 
= <<<XML
<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<document>
<animal>koala</animal>
<animal>kangaroo</animal>
<animal>wombat</animal>
<animal>wallaby</animal>
<animal>emu</animal>
<animal>kiwi</animal>
<animal>kookaburra</animal>
<animal>platypus</animal>
<animal>funnel web</animal>
</document>
XML;

try {
/*** a new simpleXML iterator object ***/
$sxi =new SimpleXMLIterator($xmlstring);

/*** add a child ***/
$sxi->addChild('animal''Tiger');

/*** a new simpleXML iterator object ***/
$new = new SimpleXmlIterator($sxi->saveXML());

/*** iterate over the new tree ***/
foreach($new as $val)
{
echo 
$val.'<br />';
}
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

The script above produces a list like this:

  • koala
  • kangaroo
  • wombat
  • wallaby
  • emu
  • kiwi
  • kookaburra
  • platypus
  • funnel web
  • Tiger

Note that when the new we call the saveXML() method that the XML is saved as an XML string once more. If we wish to iterate over the new string, we need to use another iterator instance. The saveXML method is an alias of asXML and was added to remove the inconsistency with domXML::saveXML. Adding an attribute is just as easy with the addAttribute method as shown here.


<?php 
$xmlstring 
=<<<XML
<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<document>
<animal>koala</animal>
<animal>kangaroo</animal>
<animal>wombat</animal>
<animal>wallaby</animal>
<animal>emu</animal>
<animal>kiwi</animal>
<animal>kookaburra</animal>
<animal>platypus</animal>
<animal>funnel web</animal>
</document>
XML;

try {
/*** a new simpleXML iterator object ***/
$sxi =new SimpleXMLIterator($xmlstring);

/*** add an attribute with a namespace ***/
$sxi->addAttribute('id:att1''good things''urn::test-foo');

/*** add an attribute without anamespace ***/
$sxi->addAttribute('att2''no-ns');

echo 
htmlentities($sxi->saveXML());
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

For demonstration purposes htmlentities() has been used to show the actual XML data and not just the values. The resulting XML string now looks like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <document xmlns:id="urn::test-foo" id:att1="good things" att2="no-ns"> <animal>koala</animal> <animal>kangaroo</animal> <animal>wombat</animal> <animal>wallaby</animal> <animal>emu</animal> <animal>kiwi</animal> <animal>kookaburra</animal> <animal>platypus</animal> <animal>funnel web</animal> </document>

Caching Iterator

The caching Iterator is a forward seeking iterator and is useful for lookng forward to check if there is a valid next item.

This note from the PHP source best describes what it can do

  • note If you want to convert the elements into strings and the inner
  • * Iterator is an internal Iterator then you need to provide the
  • * flag CIT_CALL_TOSTRING to do the conversion when the actual element
  • * flag CALL_TOSTRING to do the conversion when the actual element
  • * is being fetched. Otherwise the conversion would happen with the
  • * already changed iterator. If you do not need this then it you should
  • * omit this flag because it costs unneccessary work and time.

Once again we will see what methods are available to the iterator.


<?php
/*** list all class methods ***/
foreach( get_class_methods(CachingIterator) as $methodName)
{
echo 
$methodName.'<br />';
}
?>

From the above we get a list like this...

  • __construct
  • rewind
  • valid
  • key
  • current
  • next
  • hasNext
  • __toString
  • getInnerIterator
  • getFlags
  • setFlags
  • offsetGet
  • offsetSet
  • offsetUnset
  • offsetExists
  • getCache

Some of the above methods will look very familiar by now. These include rewind(), valid(), current() and the usual suspects. But the CachingIterator also brings a few additions with hasNext and __toString and others. Lets begin with the hasNext() method. For the sake of example, we want our array to be formatted for use in a CSV file. This means we need a comma (,) after each member, except the last one. Traditionally we would have needed to do something like this...


<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

/*** set a counter ***/
$c 1;

/*** count how many members are in the array ***/
$count count($array);

/*** loop of the array ***/
foreach($array as $value)
{
echo 
$value;
if(
$c $count)
{
echo 
',';
$c++;
}
}
?>

Apart from being plain ugly, the use of foreach here creates memory issues when it creates a copy of the array. Because SPL iterators know only one element at a time, it is far more efficient. Lets see how SPL deals with the same task.


<?php
/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create a new object ***/
$object = new CachingIterator(new ArrayIterator($array));
foreach(
$object as $value)
{
echo 
$value;
if(
$object->hasNext())
{
echo 
',';
}
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

This makes for much cleaner code by ridding us of counters and the like, we could further make this code more re-usable by extending the CachingIterator class. This would be most helpful if this task was something that needed to be done on a regular basis.


<?php

class addComma extends CachingIterator{

/*** add a comma if there is more array members ***/
function current(){
if(
parent::hasNext())
{
return 
parent::current().',';
}
 else
{
return 
parent::current();
}
}

/*** end of class ***/

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create a new object ***/
$object = new addComma(new ArrayIterator($array));
foreach(
$object as $value)
{
echo 
$value;
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

We see from the above script that the same result is achieved but the code base is now a re-usable class extension. If we wanted access to the inner iterator, ie. ArrayIterator, we could use the getInnerIterator() method to do so.


<?php

class addComma extends CachingIterator{
/*** overload the current method ***/
function current(){
if(
parent::hasNext())
{
return 
parent::current().',';
}
 else
{
return 
parent::current();
}
}

/*** end of class ***/

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create a new object ***/
$object = new addComma(new ArrayIterator($array));
$object->getInnerIterator()->offsetUnset(5);
foreach(
$object as $value)
{
echo 
$value;
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

The above results produce a list of animals minus the array member 5, the kiwi. we used the getInnerIterator() method to directly access the inner iterator, in this case the ArrayIterator, which gives us access to all the fun methods associated with that iterator. However, this still may not be optimal for re-use. We could call the inner iterator from our extended class and deal with the offending kiwi there, thus streamlining our userland codebase.

 <?php

class addComma extends CachingIterator{

function 
__construct($it){
/*** pass the iterator to the parent ***/
parent::__construct($it);
/*** call the parent getInnerIterator class method offSetUnset() ***/
parent::getInnerIterator()->offSetUnset(5);
}

function 
current(){
if(
parent::hasNext())
{
return 
parent::current().',';
}
 else
{
return 
parent::current();
}
}

/*** end of class ***/

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

try {
/*** create a new object ***/
$object = new addComma(new ArrayIterator($array));
foreach(
$object as $value)
{
echo 
$value;
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

Now we see how extensible SPL really is. The same concepts can be used with all iterators when extending. The CachingIterator also has a number of flags for use with the iterator. They are..

  • CALL_TOSTRING
  • TOSTRING_USE_KEY
  • TOSTRING_USE_CURRENT
  • TOSTRING_USE_INNER

The CALL_TOSTRING flag is quite simple as it simply calls __toString() method.


<?php

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');
/*** a new caching iterator object ***/
$cacheObj = new CachingIterator(new ArrayIterator($array), 0);
try
{
/*** set the flag __toString ***/
$cacheObj->setFlags(CachingIterator::CALL_TOSTRING);
/*** loop through the object ***/
foreach($cacheObj as $v)
 {
 
/*** object is now cast to string ***/
 
echo(string)$cacheObj.'<br />';
 }
}
catch (
Exception $e)
{
echo 
'Exception: ' $e->getMessage();
}
?>

As seen above, the object has been made into a string representation and can be used directly as a string. Note also we have used the CachingIterator::setFlags() method to set the flag. We use the TOSTRING_USE_KEY in the same way. This flag will call the array keys as shown below.


<?php

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');
/*** a new caching iterator object ***/
$cacheObj = new CachingIterator(new ArrayIterator($array), 0);
try
{
/*** set the flag __toString ***/
$cacheObj->setFlags(CachingIterator::TOSTRING_USE_KEY);
/*** loop through the object ***/
foreach($cacheObj as $v)
 {
 
/*** object is now cast to string ***/
 
echo(string)$cacheObj.'<br />';
 }
}
catch (
Exception $e)
{
echo 
'Exception: ' $e->getMessage();
}
?>

The TOSTRING_USE_KEY flag has been used to grab the array keys from the caching iterator object, and force them into a string representation. So the result from the above code will yield the numbers 0-7. Not too exciting, but remember, iterators can be used to traverse ANY aggregate structure, so if the above principles were applied to an XML document, or a result set from a database query, you can quickly see how this might be useful. Finally the TOSTRING_USE_CURRENT is once again used in the same manner as shown here.


<?php

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');
/*** a new caching iterator object ***/
$cacheObj = new CachingIterator(new ArrayIterator($array), 0);
try
{
/*** set the flag __toString ***/
$cacheObj->setFlags(CachingIterator::TOSTRING_USE_CURRENT);
/*** loop through the object ***/
foreach($cacheObj as $v)
 {
 
/*** object is now cast to string ***/
 
echo(string)$cacheObj.'<br />';
 }
}
catch (
Exception $e)
{
echo 
'Exception: ' $e->getMessage();
}
?>

The TOSTRING_USE_INNER flag is used to call the __toString() method from an inner iterator object. Lets put it to work and then see you the results.


<?php

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

/*** a new caching object ***/
$cacheObj = new CachingIterator(new MyArrayIterator($array), 0);
try
{
/*** set our flags ***/
$cacheObj->setFlags(CachingIterator::TOSTRING_USE_INNER);
/*** travers the iterator ***/
foreach($cacheObj as $v)
{
/*** objects are now strings ***/
echo (string)$cacheObj.'<br />';
}
}
catch (
Exception $e)
{
echo 
'Exception: ' $e->getMessage() . "\n";
}

class 
MyArrayIterator extends ArrayIterator
{
function 
__toString(){
return 
$this->key() . ':' $this->current();
}

}
/*** end of class ***/
?>

The TOSTRING_USE_INNER flag has been set and so the __toString() method has been called from the inner interator object. The inner iterator object in this case is formed from the thte MyArrayIterator class which extends ArrayIterator. The MyArrayIterator::__toString() method has been used to return a string representation of the current key and value. This results in a list such as this.

  • 0:koala
  • 1:kangaroo
  • 2:wombat
  • 3:wallaby
  • 4:emu
  • 5:kiwi
  • 6:kookaburra
  • 7:platypus

Note: you can only use ONE flag for the __toString() method. If you try to use more an exception will be thrown.


<?php

class MyCachingIterator extends CachingIterator{

function 
__construct(Iterator $it$flags 0){
parent::__construct($it$flags);
}

function 
test($offsetArray){
foreach(
$offsetArray as $offset)
{
if(
$this->offsetExists($offset));
{
echo 
$offset.' == '$this->offsetGet($offset).'<br />';
}
}
}
}
/*** end of class ***/

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''NZ'=>'kiwi''kookaburra''platypus');
try {

$it = new MyCachingIterator(new ArrayIterator($array), CachingIterator::FULL_CACHE);

/*** an array of checks to run ***/
$offsetArray = array(02'NZ'6);

/*** read all into cache ***/
foreach($it as $v);

/*** load the offset array ***/
$it->test($offsetArray);
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

There may be a bit to digest from the code above. But essentially it checks the offsets in offsetArray and prints the values like this.

  • 0 == koala
  • 2 == wombat
  • NZ == kiwi
  • 3 == wallaby
  • 6 == platypus

So, there is a little WTF value here. We have used offsetExists to check the values of the offsetArray and offsetGet to print the value of the offset. You will have noticed that the offset of 6 is platypus. This is because there is no offset of 7 as the fifth offset is NZ with the value of kiwi. So offset 5 becomes the next offset and 6 is the final offset in this array.

The offsetUnset() method has been left to now for the reason of explaining some internal behaviour that may not be apparent other wise. When an offset is unset, only the value of the offset is unset and the offset/key remains. In the cache however, the offset and key are removed. The script below shows clearly the behavior when the cache is called with the getCache() method.


<?php

class MyCachingIterator extends CachingIterator{

function 
__construct(Iterator $it$flags 0){
parent::__construct($it$flags);
}

function 
test($offsetArray){
/*** check if the offset exists ***/
if($this->offsetExists('NZ'));
{
/*** unset the offset ***/
$this->offsetUnset('NZ');
}
/*** loop over the $offset array ***/
foreach($offsetArray as $offset)
{
/*** get the corresponding value of the offset ***/
echo $offset.' == '$this->offsetGet($offset).'<br />';
}
}
}
/*** end of class ***/

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''NZ'=>'kiwi''kookaburra''platypus');
try {

$it = new MyCachingIterator(new ArrayIterator($array), CachingIterator::FULL_CACHE);

/*** an array of checks to run ***/
$offsetArray = array(02'NZ'6);

/*** read all into cache ***/
foreach($it as $v);

/*** load the offset array ***/
$it->test($offsetArray);

/*** show the cache contents ***/
echo '<h3>Get Cache</h3>';
foreach(
$it->getCache() as $offset=>$value)
{
echo 
'Cache: '.$offset.' == '.$value.'<br />';
}
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}
?>

The results from the above code will produce the following output.

0 == koala
2 == wombat
NZ ==
3 == wallaby
6 == platypus

Get Cache

Cache: 0 == koala
Cache: 1 == kangaroo
Cache: 2 == wombat
Cache: 3 == wallaby
Cache: 4 == emu
Cache: 5 == kookaburra
Cache: 6 == platypus

Note that in the first iteration, the NZ key remains and the value has been removed. When the cache itself is checked with getCache we see the true picture of the current state of the offset keys and values. The array object has been re-indexed with the NZ offset key and value removed.

Limit Iterator

As the name suggests, the LimitIterator allows the coder to limit the return values. Lets see the methods available to the LimitIterator.


<?php 
/*** list all class methods ***/
foreach( get_class_methods(LimitIterator) as $methodName)
{
echo 
$methodName.'<br />';
}
?>

The results from above show us the following methods available to the LimitIterator

  • __construct
  • rewind
  • valid
  • key
  • current
  • next
  • seek
  • getPosition
  • getInnerIterator

Once again we see the usual suspects os valid() key() current() next() for iteration. What makes the LimitIterator funky is the seek() method and getPosition() method. The LimitIterator is an outer iterator and takes an iterator as a parameter for the construct. The LimitIterator allows you to specify the offset and the number of results returned. I can already hear the gears grinding as you think how this might be ideal for pagination of an aggregate structure such as database results, or an XML heirachy. This sort of functionality allows us to mimic the behaviour of the SQL LIMIT clause with the use of an offset and limit,except we can use it over any aggregate structure.


<?php

/*** the offset value ***/
$offset 3;

/*** the limit of records to show ***/
$limit 2;

/*** a simple xml tree ***/
 
$xmlstring = <<<XML
<?xml version = "1.0" encoding="UTF-8" standalone="yes"?>
<document>
<animal>koala</animal>
<animal>kangaroo</animal>
<animal>wombat</animal>
<animal>wallaby</animal>
<animal>emu</animal>
<animal>kiwi</animal>
<animal>kookaburra</animal>
<animal>platypus</animal>
</document>
XML;

try{
/*** a new limitIterator object ***/
$it = new LimitIterator(new SimpleXMLIterator($xmlstring), $offset$limit);
/*** show the results ***/
foreach($it as $r)
{
// output the key and current array value
echo $it->key().' -- '.$it->current().'<br />';
}
}
catch(
Exception $e)
{
echo 
$e->getMessage();
}

The above code will produce a list of like this:

  • animal -- wallaby
  • animal -- emu

Using the offset and limit variables, we have successfully extracted the XML data by positioning the internal pointer to the third item in the tree (offset)and fetching the next two items (limit). The offset an limit variables could come from POST or GET to aid in your application pagination. We saw here the use of the SimpleXMLiterator as our inner iterator. The SimpleXMLIterator can do much more. You can read more in SimpleXML Iterator chapter. The LimitIterator also allows the checking of the position of the internal pointer to check where you are in your iteration. Here we will switch to an ArrayIterator as the inner iterator to see your we can get the position of the internal pointer with the getPosition() method.


<?php
/*** the offset value ***/
$offset 3;

/*** the limit of records to show ***/
$limit 2;

$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

$it = new LimitIterator(new ArrayIterator($array), $offset$limit);

foreach(
$it as $k=>$v)
{
echo 
$it->getPosition().'<br />';
}
?>

The code above will give us the results 3 and 4. The LimitIterator::getPosition() method has given the current position within the iterator. We can also use the seek() method to go directly to a position within the iterator. When called, the seek() method implements the SeekableIterator to find the required position and exposing the value to the current() method.


<?php

/*** the offset value ***/
$offset 4;

/*** the limit of records to show ***/
$limit 6;

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

$it = new LimitIterator(new ArrayIterator($array), $offset$limit);

try
{
$it->seek(3);
echo 
$it->current();
}
catch(
OutOfBoundsException $e)
{
echo 
$e->getMessage() . "<br />";
}
?>

If you run the above code you will get an error message like this:

Cannot seek to 3 which is below the offset 4

This is because we have incorrectly specified the seek parameter and an OutOfBoundsException is thrown. We then catch the exception and many handle the error as we wish. Lets try again with some valid values.


 <?php

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

$it = new LimitIterator(new ArrayIterator($array));

try
{
$it->seek(5);
echo 
$it->current();
}
catch(
OutOfBoundsException $e)
{
echo 
$e->getMessage() . "<br />";
}
?>

This time we have removed the offset and the limit and the whole inner iterator is exposed. The seek() method has been used to direct the internal pointer to the offset of 5. It is then a simple matter to echo the current() value, in this case the result is kiwi. Like all outer iterators, the LimitIterator allows us access to the inner iterator with the getInnerIterator() method.


<?php

/*** the offset value ***/
$offset 2;

/*** a simple array ***/
$array = array('koala''kangaroo''wombat''wallaby''emu''kiwi''kookaburra''platypus');

/*** a new LimitIterator object ***/
$it = new LimitIterator(new ArrayIterator($array), $offset);

/*** access the inner iterator method offsetUnset() ***/
$it->getInnerIterator()->offsetUnset(5);

try
{
foreach(
$it as $value)
{
echo 
$value.'<br />';
}
}
catch(
OutOfBoundsException $e)
{
echo 
$e->getMessage() . "<br />";
}
?>

This time we have omitted the limit parameter and used only the offset. This will allow the LimitIterator to begin at the offset and traverse to the end of the structure. We have also accessed the inner iterator (ArrayIterator) with the getInnerIterator() method and called ArrayIterator::offsetUnset() method to remove the kiwi from our lives.



?>


?>

SplFileObject

Originally design for a special task the SplFileObject has proved a useful addition to the SPL extension by providing us once again with a standard OO interface to handle files and iterations. In the bad old days our code would have looked like this:.


<?php

// read the file into an array
$lines file('myfile.txt');

// Loop through our array, show line numbers and line.
foreach ($lines as $line_num => $line)
    {
     echo 
'Line: '$line_num .' -> ' $line'<br />'."\n";
    }

?>

The above codes creates an array and then loops through this array to print the lines and numbers. With the advent of SplFileObject we can treat our file as though it were an object and use SPL iterators to traverse the file.


<?php

try{
    
// create a new spl file object from a file
$file = new SplFileObject("/usr/local/apache/logs/access_log");
    
// check if for validity
while($file->valid())
{
        
// echo the current line
echo $file->current().'<br />';
        
// increment the iterator
$file->next();
}
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

Now we have a nice Object Oriented approach that should be familiar if you have read the rest of this tutorial. If follows the same standard approach. This is the true power of SPL in giving us flexibility within a constrained and standardised development environment. Lets make our code a little faster with foreach.


<?php

try{
// iterate directly over the object
foreach( new SplFileObject("/usr/local/apache/logs/access_log") as $line)
// and echo each line of the file
echo $line.'<br />';
}
catch (
Exception $e)
{
echo 
$e->getMessage();
}
?>

Of course the SplFileObject does not limit us in our using of all the filesystem functions we have been acustom to in the past. Like the filesystem functions we have great flexibility in how we manipulate our file data. Here we see an example using seek():


<?php

try{
    
$file = new SplFileObject("/usr/local/apache/logs/access_log");

    
$file->seek);

    echo 
$file->current();
}
catch (
Exception $e)
    {
    echo 
$e->getMessage();
    }
?>

To see a list of all the avaiable methods for SplFileObject we once again simply do this:


<?php

foreach( get_class_methods(SplFileObject) as $methodName)
{
echo 
$methodName.'<br />';
}
?>

The above snippet will produce a list featuring these methods.

  • __construct
  • getFilename
  • isDot
  • rewind
  • valid
  • key
  • current
  • next
  • __toString
  • getPath
  • getPathname
  • getPerms
  • getInode
  • getSize
  • getOwner
  • getGroup
  • getATime
  • getMTime
  • getCTime
  • getType
  • isWritable
  • isReadable
  • isExecutable
  • isFile
  • isDir
  • isLink
  • getFileInfo
  • getPathInfo
  • openFile
  • setFileClass
  • setInfoClass

If you have read the chapter on the Directory Iterator then, this list will be familiar to you from the SplFileInfo iterator. The SplFileObject extends SplFileInfo. It is defined as such:
class SplFileObject extends SplFileInfo implements RecursiveIterator, SeekableIterator
Because the SplFileObject extends SplFileInfo all those class methods are avaliable to it, plus the added goodness of the recursive Iterator and the Seekable Iterator. This makes for some exciting functionality not otherwise available with the SplFileInfo class alone.

Conclusions

As shown in these examples, SPL provides us with a toolkit to iterate over almost any aggregate structure, The benifits of this is that it is a Standard and so can be used by all to provide better coding practices. You will see often in you code that much of it is based on creating and traversing aggregate stuctures as those shown above. We hope this gets you started on your way to implementing this in your code base. For more information there is always the PHP manual section on SPL

Credits

Many thanks to Marcus Boerger for his help with the code in this tutorial and quick fixes to bugs in the PHP 5.1.0 core as we came across them. Marcus is a PHP core developer and the creator of SPL. He has provided excellent docs that show the inner workings of SPL at http://www.php.net/~helly/php/ext/spl/.