Introduction to SPL
Introduction to Standard PHP Library (SPL)
By Kevin Waterson
Contents
- What is SPL
- What are Iterators
- Apply Callback Function to Iterator
- ArrayAccess
- Directory Iterator
- Extending the DirectoryIterator
- ArrayObject
- Array Iterator
- RecursiveArrayIterator
- Something useful
- Overloading
- Filter Iterator
- Simple XML Iterator
- Caching Iterator
- Limit Iterator
- SplFileObject
- SPL Autoload
- Conclusions
- Credits
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.' -> '.$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 addCaps( Iterator $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.
/*** 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'.
/*** 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.
/*** 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.
/*** 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:
/*** 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().' -> '.$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:
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.
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($it, self::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
<?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 __construct( Iterator $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() % 2 != 1)
{
return false;
}
$d = 3;
$x = sqrt($this->current());
while ($this->current() % $d != 0 && $d < $x)
{
$d += 2;
}
return (($this->current() % $d == 0 && $this->current() != $d) * 1) == 0 ? 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) 