Iterators in the Simplest Sense: Traversing Data Structures in PHP 5

Welcome to the final part of the series “Iterators in the Simplest Sense.” In this set of articles, you’ll learn the basic concepts of iterators in PHP 4 and PHP 5. The overall learning experience is strongly focused on the practical sense of the topic, so you can start quickly using iterators within your own PHP applications.

Introduction

In order to remind you of some of the key concepts I explained in my previous tutorial of this series, let me mention briefly a few crucial points regarding iterators in PHP 4. As you’ll probably recall, during the second article, I illustrated with a step-by-step approach how different data structures, specifically arrays, text files and MySQL result sets, can be easily traversed by the same set of methods, which were originally declared within a generic “Iterator” class, seated on top of the classes’ hierarchy.

This approach allowed me to rapidly construct different iterator classes, which came in helpful for iterating over MySQL datasets and simple text files, utilizing the functionality provided by each of the methods defined within a specific array iterator class. As you hopefully learned from the hands-on examples you saw, once this class was created, building in distinct iterating classes is just a matter of deriving different subclasses from it, and specifically implementing the corresponding methods in consonance with the type of data being traversed.

Right, with reference to the content of the first two articles of this series, I think you’ll have some nice material to keep you busy for long time coding iterators in PHP 4. Now, if you’re one of the many developers working with PHP 5, things can be even more interesting, since PHP 5 offers native support for iterators through the SPL (Standard PHP Library).

Considering this propitious development scenario, in this last article I’ll explore the implementation of PHP 5 iterators, by utilizing some of the embedded classes that integrate the powerful SPL package. Thus you can learn quickly how to include these programming structures inside your PHP applications.

After introducing the subject of this article, it’s time to move forward, in order to start using iterators in PHP 5. Let’s go!

{mospagebreak title=Working with iterators in PHP 5: using some predefined SPL classes}

When PHP 5 was initially released, it included a strong sense of standardization introduced in the core package. This can be a real time saver when common programming issues must be solved through proven, standard solutions. That’s precisely the case with iterators, since the SPL package includes many predefined iterating classes that can be used for traversing different data structures, without having to reinvent the wheel over and over again.

In this particular case, in order to illustrate how data of a distinct type can be accessed by the same set of methods, what I’ll do next is use the “ArrayObject” class, included within the SPL package, to construct some comprehensive iterators in PHP 5. Of course, here there’s plenty of room to experiment with other predefined classes, such as “ArrayIterator” or “DirectoryIterator,” among others, so if you’re interested in learning more on the subject, take a look at the PHP manual. In my opinion it’s the best resource for further reading.

Now, after a short theoretical introduction to iterators in PHP 5, it’s time to illustrate a simple process for building a file iterator class, similar to the one I showed in my second article. The definition for this class is shown below:

class FileIterator {
    private $iterator;
    public function __construct($file){
        if(!file_exists($file)){
            throw new Exception(‘Invalid input file’);
        }
        // get ArrayObject
        $arrayobj=new ArrayObject();
        // get Iterator object
        $this->iterator=$arrayobj->getIterator();
        $lines=file($file);
        foreach($lines as $line){
            $arrayobj[]=$line;
        }
    }
    // get first line of file
    public function rewind(){
        return $this->iterator->rewind();
    }
    // get current line of file
    public function current(){
        if($this->iterator->valid()){
            return $this->iterator->current();
        }
    }
    // get next line of file
    public function next(){
        if($this->iterator->valid()){
            return $this->iterator->next();
        }
    }
    public function seek($pos){
        if(!is_int($pos)||$pos<0){
            throw new Exception(‘Invalid position’);
        }
        return $this->iterator->seek($pos);
    }
    public function count(){
        return $this->iterator->count();
    }
}

If you study the above “FileIterator” class, you’ll probably see that its source code is really easy to follow. As you’ve seen, this class exposes the most usual methods for traversing any data structure, such as the respective “rewind(),” “current()” and “next()” methods. Even when this may seem like a trivial thing, please take a look at the signature for the corresponding class constructor, which is listed below:

public function __construct($file){
    if(!file_exists($file)){
        throw new Exception(‘Invalid input file’);
    }
    // get ArrayObject
    $arrayobj=new ArrayObject();
    // get Iterator object
    $this->iterator=$arrayobj->getIterator();
    $lines=file($file);
    foreach($lines as $line){
        $arrayobj[]=$line;
    }
}

As you can see in the above snippet, the constructor accepts the text file for being traversed as the only input parameter of the class. Now here’s where things get really interesting: notice how this method first uses an instance of the predefined “ArrayObject” class, that is “$arrayobj,”, then invokes the “getIterator()” method, and finally adds each line of the input file as new elements of the mentioned array.

After storing the contents of the text file as elements of the “ArrayObject,” traversing its structure is reduced to calling each of its methods, as indicated a few lines above. That’s it. Don’t tell me that wasn’t really simple!

Fine, at this point you know how to create a simple “FileIterator” class in PHP 5, which can be used as a building block for more sophisticated PHP applications. Now, the next step consists of illustrating with a simple example how this iterator class should be used for traversing a specific text file. Therefore, join me in the next section to find out how this process is performed.

{mospagebreak title=Traversing text files the easy way: the brand new FileIterator class}

In order to demonstrate the functionality of the “FileIterator” class that I defined before, I’ll first create a basic text file, which will be inputted right into the constructor, and second spawn an object from this class. In turn, all the respective methods will be called up, providing an easy method for navigating back and forward across the data file. Please look at the following basic sample text file:

This is line 1 of the data file that is being traversed by the FileIterator class.
This is line 2 of the data file that is being traversed by the FileIterator class.
This is line 3 of the data file that is being traversed by the FileIterator class.
This is line 4 of the data file that is being traversed by the FileIterator class.
This is line 5 of the data file that is being traversed by the FileIterator class.
This is line 6 of the data file that is being traversed by the FileIterator class.
This is line 7 of the data file that is being traversed by the FileIterator class.
This is line 8 of the data file that is being traversed by the FileIterator class.
This is line 9 of the data file that is being traversed by the FileIterator class.
This is line 10 of the data file that is being traversed by the FileIterator class.

And next, analyze the PHP script that traverses the above file:

try{
    $fIterator=new FileIterator(‘test.txt’);
    // reset pointer to beginning of file
    $fIterator->rewind();
    // display current line of file
    echo $fIterator->current();
    // move to next line of file
    $fIterator->next();
    // display current line of filet
    echo $fIterator->current();
    // display number of lines in file
    echo $fIterator->count();
    // move file pointer to third line
    $fIterator->seek(3);
    // display third line
    echo $fIterator->current();
}
catch(Exception $e){
    echo $e->getMessage();
    exit();
}

As the above example illustrates, a new instance of the “FileIterator” class is created, and then all its methods are called in sequence, in order to iterate over the “test.txt” file you just saw. Notice the ease of fetching, seeking and counting file data respectively, once the iterator is available inside the script. In this case I enclosed the whole sample code within a regular “try-catch” block, but more complex settings can be used to handle potential exceptions.

At this stage, hopefully you realize how simple it is to create a file iterator class in PHP 5. This example should be a good starting point, just in case you might want to experiment with some other preexisting classes included in the SPL package. Here, I only used the “ArrayObject” class, and it helped me to skip the annoying task of repeatedly defining the same class methods.

After demonstrating a functional implementation of the “FileIterator” class, a good corollary for this section would be building another iterating class, which can be utilized for traversing MySQL result sets. Bearing in mind this idea, in the next few lines, I’ll create such a class, to help you understand how database records can be handled by a data traversing class. Go ahead and read the next section.

{mospagebreak title=Traversing database result sets: building a MySQL iterator class}

As I said before, my last example on building iterator classes in PHP 5 comprised a simple yet efficient MySQL iterator class. If you’re planning to create a dataset paging system without appealing to hard-to-code routines, this iterator might fit your needs. But, first of all, let me show you how this class actually looks. Its definition is as follows:

class ResultIterator{
    private $iterator;
    public function __construct($result){
        if(get_resource_type($result)!=’mysql result’){
            throw new Exception(‘result must be a MySQL result
set’);
        }
        // get ArrayObject
        $arrayobj=new ArrayObject();
        // get Iterator object
        $this->iterator=$arrayobj->getIterator();
        while($row=mysql_fetch_row($result)){
            $arrayobj[]=implode(”,$row);
        }
    }
    // reset pointer of MySQL result set
    public function rewind(){
        return $this->iterator->rewind();
    }
    // get current row
    public function current(){
        if($this->iterator->valid()){
            return $this->iterator->current();
        }
    }
    // get next row
    public function next(){
        if($this->iterator->valid()){
            return $this->iterator->next();
        }
    }
    // seek row
    public function seek($pos){
        if(!is_int($pos)||$pos<0){
            throw new Exception(‘Invalid position’);
        }
        return $this->iterator->seek($pos);
    }
    // count rows
    public function count(){
        return $this->iterator->count();
    }
}

As you can see, the definition for the above “ResultIterator” class reaffirms the concepts that I explained at the beginning of this article. In this case I built this iterator class by redefining the same set of methods exposed by the previous “FileIterator.” Doing so, it’s possible to access a given MySQL data set by using the identical bunch of methods (also called an interface).

Regarding the definition of the class constructor, it’s clear to see that it reuses the “ArrayObject” class, in order to store each dataset row as a new element of the pertinent array structure. Also, the following checking block:

if(get_resource_type($result)!=’mysql result’){
    throw new Exception(‘result must be a MySQL result set’);
}  

makes sure that only valid MySQL result sets are passed as input parameters to the constructor. Of course, as I explained before, the remaining “rewind(),” “current(),” “next()” methods, etc., are properly redefined within the class; thus they can be used to iterate over the corresponding database result set.

After defining the previous “ResultIterator” class, here’s a possible implementation, in this case integrated with a couple of MySQL processing classes:

// define ‘MySQL’ class
class MySQL{
    private $host;
    private $user;
    private $password;
    private $database;
    private $connId;
    // constructor
    function __construct($options=array()){
        if(!is_array($options)){
            throw new Exception(‘Connection options must be an
array’);
        }
        foreach($options as $option=>$value){
            if(empty($option)){
                throw new Exception(‘Connection parameter cannot
be empty’);
            }
            $this->{$option}=$value;
        }
        $this->connectDb();
    }
    // private ‘connectDb()’ method
    private function connectDb(){
        if(!$this->connId=mysql_connect($this->host,$this-
>user,$this->password)) {
            throw new Exception(‘Error connecting to MySQL’);
        }
        if(!mysql_select_db($this->database,$this->connId)){
            throw new Exception(‘Error selecting database’);
        }
    }
    // public ‘query()’ method
    public function query($sql){
        if(!$result=mysql_query($sql)){
            throw new Exception(‘Error running query ‘.$sql.’ ‘.mysql_error());
        }
        return new Result($this,$result);
    }
}
// define ‘Result’ class
class Result{
    private $mysql;
    private $result;
    // constructor
    public function __construct($mysql,$result){
        $this->mysql=$mysql;
        $this->result=$result;
    }
    // public ‘fetch()’ method
    public function fetch(){
        return mysql_fetch_array($this->result,MYSQL_ASSOC);
    }
    // public ‘count()’ method
    public function count(){
        if(!$rows=mysql_num_rows($this->result)){
            throw new Exception(‘Error counting rows’);
        }
        return $rows;
    }
    // public ‘get_insertId()’ method
    public function getInsertId(){
        if(!$insId=mysql_insert_id($this->mysql->connId)){
            throw new Exception(‘Error getting insert ID’);
        }
        return $insId;
    }
    // public ‘seek()’ method
    public function seek($row){
        if(!int($row)&&$row<0){
            throw new Exception(‘Invalid row parameter’);
        }
        if(!$row=mysql_data_seek($this->mysql->connId,$row)){
            throw new Exception(‘Error seeking row’);
        }
        return $row;
    }
    // public ‘getAffectedRows()’ method
    public function getAffectedRows(){
        if(!$rows=mysql_affected_rows($this->mysql->connId)){
            throw new Exception(‘Error counting affected rows’);
        }
        return $rows;
    }
    // public ‘getQueryResource()’ method
    public function getQueryResource(){
        return $this->result;
    }
}
// define ‘ResultIterator’ class
class ResultIterator{
    private $iterator;
    public function __construct($result){
        if(get_resource_type($result)!=’mysql result’){
            throw new Exception(‘result must be a MySQL result set’);
                        }
        // get ArrayObject
        $arrayobj=new ArrayObject();
        // get Iterator object
        $this->iterator=$arrayobj->getIterator();
        while($row=mysql_fetch_row($result)){
            $arrayobj[]=implode(”,$row);
        }
    }
    // reset pointer of MySQL result set
    public function rewind(){
        return $this->iterator->rewind();
    }
    // get current row
    public function current(){
        if($this->iterator->valid()){
            return $this->iterator->current();
        }
    }
    // get next row
    public function next(){
        if($this->iterator->valid()){
            return $this->iterator->next();
        }
    }
    // seek row
    public function seek($pos){
        if(!is_int($pos)||$pos<0){
            throw new Exception(‘Invalid position’);
        }
        return $this->iterator->seek($pos);
    }
    // count rows
    public function count(){
        return $this->iterator->count();
    }
}
// implement ‘ResultIterator’
try{
    // connect to MySQL
    $db=new MySQL(array(‘host’=>’host’,'user’=>’user’,'password’=>’password’,
‘database’=>’database’));
    // get result set
    $result=$db->query(‘SELECT * FROM mytable’);
    // use ‘ResultIterator’ class
    $rIterator=new ResultIterator($result->getQueryResource());
    // reset pointer to beginning of result set
    $rIterator->rewind();
    // display current row of result set
    echo $rIterator->current();
    // move to next row of result set
    $rIterator->next();
    // display current row of result set
    echo $rIterator->current();
    // display number of rows in result set
    echo $rIterator->count();
    // move file pointer to third row in result set
    $rIterator->seek(3);
    // display third row in result set
    echo $rIterator->current();
}
catch(Exception $e){
    echo $e->getMessage();
    exit();
}

That’s it. The above example shows a simple approach for traversing a MySQL dataset, after connecting to the server and fetching some rows from a hypothetical database table. Similar to the script I wrote for implementing the “FileIterator” class, all the methods for moving back and forth across the returned dataset are called in sequence. In addition, I listed the classes that connect to MySQL and fetch rows from the corresponding database table, therefore you can see how each class fits with each other. Quite simple, right?

Wrapping up

Unfortunately, this series has concluded now. If you’ve been reading each of the parts, then I hope you’ve acquired a deeper grounding in iterators in PHP 4/ PHP 5.

With all the written examples, I demonstrated how to use the iterator pattern for traversing different data structures, that is arrays, flat files and MySQL result sets, by utilizing nearly the same sets of methods, which is a good approach to coding standard classes for accessing many data types. As for other design patterns, mastering them takes a while, so be patient and digest the concepts very slowly. The reward is really worth it!

Google+ Comments

Google+ Comments