The Standard PHP Library, Part 2

In our second article on the standard PHP library, David Fells explains the new Arrray object, introduces Iterators and the ArrayIterator, and discusses some practical examples of their usage. This article assumes a knowledge of how Exceptions work in PHP 5.

Introduction

In the first installment of my series on SPL, we talked about Exceptions. If you have not read it and are not yet familiar with how exceptions work in PHP 5, please do so here (http://www.devshed.com/showblog/6520/The-Standard-PHP-Library-Part-1). This article assumes knowledge of the Exception class. This installment will cover the new Array object, introduce Iterators and the ArrayIterator, and discuss some practical examples of their usage.

An iterator is, as the name implies, an object that traverses the contents of another object. The object may be a simple array data type or it may be a complex class, but by using iterators, we can standardize the way we traverse objects. The more important concept, however, is providing a way to traverse an object without exposing its internal data structure. In addition to providing a standard interface for object traversal and keeping the internal structure of the object being traversed private, it is common to iterate an object using a filter or to have multiple pending traversals on a single object. Using an iterator allows us to do all this.

The big picture objective behind using special objects such as Iterators is to achieve what is called loose coupling. Loose coupling is present when the internal behavior of objects can change without affecting the objects that interact with them. It also means that private class data is kept private and is not directly accessible to outside objects. This allows classes to store internal data in whatever way they like without forcing other objects to make assumptions about data storage when attempting to directly access class data. The new features in PHP 5 and the introduction of the SPL allow PHP developers to take advantage of these concepts in ways that were previously not possible. To get an overview of the new features in PHP 5 if you are not already familiar with them, have a look at my article: “What’s New in PHP 5″ (http://www.devshed.com/c/a/PHP/Whats-New-in-PHP-5/).

We will discuss the new ArrayObject class as a precursor to our discussion on iterators, as the ArrayObject class serves as an excellent example for simple iteration routines.

{mospagebreak title=The Array Object}

Programmers familiar with Java and .NET will be at home with using the new ArrayObject class. The ArrayObject class has a definition like this:

class ArrayObject implements IteratorAggregate, 
ArrayAccess, Countable { public function __construct($array); public function append($value); public function count(); public function getArrayCopy(); public function getIterator(); public function offsetExists($index); public function offsetGet($index); public function offsetSet($index, $newval); public function offsetUnset($index); }

As you see in the declaration, the ArrayObject class implements three interfaces. The Countable interface requires the public method count() to be defined and allows the built in PHP count() function to be used on the object. The ArrayAccess interface is used to override array access of objects. For example, the offsetGet($index) method is used in place of the normal array style: $array[$index]. This interfaces calls for the public methods offsetExists(), offsetGet(), offsetSet() and offsetUnset(). The IteratorAggregate interface requires the public method getIterator(), which allows the calling code to use an external iterator for object traversal.

It is worth noting that the IteratorAggregate interface implements the Traversable abstract base interface, which is used to detect if a class is traversable using foreach. This interface must be implemented by either Iterator (an iterator object) or IteratorAggregate (an object to be iterated).

Let’s take a look at some sample code:

$myArray = new ArrayObject();
$myArray->append(‘a’);
$myArray->append(‘b’);
$myArray->append(‘c’);

echo ‘First Element: ‘.$myArray->offsetGet(0).'<br />';
echo ‘Second Element: ‘.$myArray->offsetGet(1).'<br />';
echo ‘Third Element: ‘.$myArray->offsetGet(2).'<br />';
echo ‘Number of Elements: ‘.$myArray->count().'<br />';

$myArray->offsetUnset(0);
$myArray->offsetSet(1, ‘a’);
echo ‘First Element: ‘.$myArray->offsetGet(0).'<br />';
echo ‘Second Element: ‘.$myArray->offsetGet(1).'<br />';
echo ‘Third Element: ‘.$myArray->offsetGet(2).'<br />';
echo ‘Number of Elements: ‘.$myArray->count().'<br />';

The first line creates the ArrayObject object and the next three lines store the values “a”, “b” and “c” in it. The next four lines demonstrate using the offsetGet() method and the count() method to retrieve values at a specified offset and to return the number of elements in the array, respectively. Next we unset the first index value by calling offsetUnset() and then change the value of the second index to “a”. We then repeat the process of outputting the values in the array and the number of elements in the array. This script will output the following:

First Element: a
Second Element: b
Third Element: c
Number of Elements: 3

First Element: 
Second Element: a
Third Element: c
Number of Elements: 2

Note that after calling unset, the index at position 0 still exists but has no value and is not included when calling count(). This seems like a counterintuitive behavior. It seems that the expected result would have been to completely remove the element – index and all – from the array and shift the other elements down. The term unset implies that after calling the method, the value is no longer set at all – meaning it is not set to NULL, 0, ” or anything else, and the index does not exist in the array. I would be interested to hear readers’ thoughts on this, as I see it as poor implementation.

{mospagebreak title=Simple Array Iterators}

The following code snippet shows a simple example of creating an ArrayObject object and an ArrayIterator object, then traversing the iterator using the three most common control structures for iteration – foreach, for and while.

$myArray = new ArrayObject();
$myArray->append(‘a’);
$myArray->append(‘b’);
$myArray->append(‘c’);
$i = $myArray->getIterator();

foreach ($i as $item) {
  print $item.'<br />';
}
$i->rewind();

for (; $i->valid(); $i->next()) {
  print $i->current().'<br />';
}
$i->rewind();

while ($i->valid()) {
  print $i->current().'<br />';
  $i->next();
}

The first line of this example creates an ArrayObject object and the next three lines call the append() method to add  “a”, “b” and “c” to the array. The fifth line gets an ArrayIterator object from the ArrayObject by calling the getIterator() method. Once we have the iterator, we are free to traverse the object in a number of different ways. The first method shown uses the foreach() control structure. Note that after traversing the iterator, we must reset it to its initial position with the rewind() method.

The second method demonstrates using an iterator with a for loop. Note that we do not need any initialization condition in the loop, but we could move the call to rewind() after the previous iteration to the for loop if desired. The condition section of the loop calls the valid() method of our iterator which returns true if more elements of the array exist after our current position and false if not. For our change section, we call the next() method which advances the iterator to the next element it contains.

The last method uses a while loop. Our condition, like the for loop above, uses the valid() method. In this example, however, we call next() after working with our data.

{mospagebreak title=Directory Iteration}

PHP developers are accustomed to using a special variable type – resource handles. Resource handles are used by PHP to identify external resources such as open files, directories and database connections. The introduction of the DirectoryIterator class allows developers to traverse a directory through a standard iterator interface, providing a high level of abstraction between the data source (the directory) and application components that use the data source. Previously a developer would perform a simple directory iteration like this :

$dh = opendir(‘images/’);
if (!$dh) 
  die(‘Unable to open directory’);

while (($resource = readdir($dh)) !== false) {
  print $resource.'<br />';
}

This rudimentary example would traverse a directory and output all the files and directories that it contains. The problem with this method of directory iteration is that the calling application has to know the data source is a directory. The following example demonstrates the use of a directory iterator. After the example, I will explain how this could be used in a non-specific context by the calling object.

try {
  $iter = new DirectoryIterator(‘images/’);
  while ($iter->valid()) {
    print $iter->current().'<br />';
    $iter->next();
  }
}
catch (Exception $e) {
  print_r($e);
}

This code should look familiar – it is identical to the array example provided above, only with a different Iterator class. You are probably wondering at this point how we have allowed our calling code to stay unaware of the actual data source. Consider this: we have an application that allows a common display component to output the contents of any iterator. The calling application sends it the right iterator object and it outputs the data. This is a highly simplified example, but it is sufficient to demonstrate intent.

try {
  TraverseIterator(new DirectoryIterator(‘images/’));
  TraverseIterator(new ArrayIterator
(new ArrayObject(array(‘a’, ‘b’, ‘c’)))); } catch (Exception $e) { print_r($e); } function TraverseIterator(Iterator $iter) { while ($iter->valid()) { print $iter->current().'<br />'; $iter->next(); } }

In this example we define a function called TraverseIterator that takes an Iterator object as its only argument. If an object that is not a child class of Iterator is passed to the function, an Exception will be thrown. Our application calls the function twice – once with a DirectoryIterator and once with an ArrayIterator. There are certainly more practical applications of this technique but this demonstrates the basic idea.

Going further, a developer could create some other object that handles creation of iterators. An object whos job is to create other objects is known as a Factory. If we create a class, IteratorFactory, we can use this to create the iterator needed and the calling application would not have to know what type of iterator to create – it would only need the object to traverse and the type of object. A detailed example of this is a subject for another article, but the concept is worth touching on here.

{mospagebreak title=Recursive Iteration}

In our example above we used an example that traversed a directory and displayed its contents. This is fine if you are simply checking the contents of a single directory, but what if you need to traverse an entire directory tree? Perhaps you may be searching for a file or simply preparing formatted output. The example below demonstrates a simple way of displaying a directory tree for a given path.

try {
  $iter = new DirectoryIterator(‘images/’);
  print WalkDirectory($iter);
}
catch (Exception $e) {
  print_r($e);
}

function WalkDirectory(DirectoryIterator $iter, $depth = 0) {
  $return = str_repeat(‘&nbsp;’, 
($depth * 5)).$iter->getPathName().'<br />'; while ($iter->valid()) { $node = $iter->current(); if ($node->isDir() && $node->isReadable() && !$node->isDot()) { $return .= WalkDirectory
(new DirectoryIterator($node->getPathname()), $depth + 1); } elseif ($node->isFile()) $return .= str_repeat
(‘&nbsp;’, ((1 + $depth) * 5)).$node->getFilename().'<br />'; $iter->next(); } return $return; }

First let us discuss the WalkDirectory function. This function takes a DirectoryIterator object as its first parameter and is required. The second parameter, depth, should not be specified by the calling application, as it is used solely to track the depth of the current traversal in the directory tree. The first line of the function grabs an appropriate amount of padding for the current depth level (we use $depth to determine how far to indent a file or directory in our output) and the current directory path name with the getPathName() method.

The next block of code beginning with the while loop is where we begin to traverse the DirectoryIterator. First we put the node from our DirectoryIterator object into a variable $node. We then test if the variable is a non-dot directory (that is, not “.” or “..”). If the current node is a directory, we append the return value of another call to WalkDirectory to our current return value. This is where the recursion we mentioned before steps in. Recursion is a topic unto itself but in short, recursion is a function calling itself with a known set of operating conditions until a desired target condition is reached. For a higher quality introduction to recursion than could be provided in this article, read Solving Problems with Recursion by Mohamed Saad (http://www.devshed.com/c/a/Practices/Solving-Problems-with-Recursion/).

If the node is not a directory then it is a file, and we simply append the appropriate amount of padding and the name of the file. The following line advances the iterator one step. Finally, on the last line, we return our return value. If we output the return value, we will see a primitive tree view of the directory we traversed.

The makers of PHP considered this scenario, though, and provided a better way of of recursively iterating structures that require recursion for complete traversal – such as directories, multidimensional arrays and any object with nested objects that use the same type of Iterator. 

Conclusion

In short, the introduction of the various interfaces related to Iterators allows PHP developers to strongly implement a sort of regimented object traversal that truly prevents outside code from seeing inside classes’ private data. The new features in PHP 5 and the introduction of the SPL are great steps toward improving developers’ abilities to develop reusable, loosely coupled classes. Keeping private data private is an important part of this and the ArrayObject, Iterator interfaces and classes come built in to assist the task of creating strong code.

As was mentioned on the previous page during our discussion of recursive iteration, there is another group of Iterators that specifically provide a method for recursive object traversal. These will be the subject of the third and final installment in this series on SPL.

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan