Using the ArrayAccess SPL Interface

In this fourth part of a six-part series, that covers the Iterator, Countable, and ArrayAccess SPL interfaces, I show how to partially implement the methods declared by the ArrayAccess SPL interface within a sample class that manipulates MySQL result sets.

This isn’t breaking news certainly, but as you may have heard, PHP 5 comes packaged with a powerful set of functions, classes and interfaces commonly know as the Standard PHP Library (SPL), which can be used for tackling typical problems present in day-to-day programming without having to reinvent the wheel every time. As with other features offered by the language, some of these classes and interfaces are more appealing than others to developers. That’s exactly the case with the Iterator, Countable and ArrayAccess interfaces, which when implemented in a clever way, allow you to treat several data collections as if they were plain array elements.

To show how the interfaces in question can be used in a real-word example, in the tutorials that preceded this one I started building a couple of MySQL abstraction classes. The first one was tasked with connecting to the database server and running queries, and the second class was responsible for fetching and manipulating result sets via a simple API.

The latter, which was a basic wrapper for the “MySQLi” native class bundled with PHP 5, implemented two of the interface mentioned above, that is the Iterator and Countable respectively. This meant the class could traverse rows in data sets by way of a “foreach” construct, and also count them by using a “count()” method, in a very similar array notation.

It should be admitted, though, that the current functionality of this sample MySQLi wrapper class could be easily enhanced by implementing the ArrayAccess SPL interface. This would allow you to fetch different rows in a given result set by specifying only their offset. That sounds quite interesting, right? Since ArrayAccess declares four methods, in this article I’m going to show you how to implement several (though not all) of those methods within the mentioned class, in this way making it slightly more functional.

Ready to learn the full details of this implementation process? Then start reading right now! 

{mospagebreak title=Review: implementing the Iterator and Countable SPL interfaces}

In case you haven’t read the previous part of this series, where I explained how to make the MySQLi wrapper class mentioned in the introduction an implementer of the Countable SPL interface, I included the complete definitions of the classes that comprise this example, starting with the one that connects to MySQL, run hard-coded queries and so forth.

Here’s how this class was originally developed:

class MySQLiWrapper extends MySQLi

{

   

   private static $_instance = NULL;

 

 

   // return Singleton instance of MySQL class

   public static function getInstance(array $config = array())

   {

       if (self::$_instance === NULL)

       {

          self::$_instance = new self($config);

       }

       return self::$_instance;

   }

  

   // private constructor

   private function __construct(array $config)

   {

        if (count($config) < 4)

        {

            throw new Exception(‘Invalid number of connection parameters’);  

        }

       list($host, $user, $password, $database) = $config;

       parent::__construct($host, $user, $password, $database);

        if ($this->connect_error)

        {

            throw new Exception(‘Error connecting to MySQL : ‘ . $this->connect_errno . ‘ ‘ . $this->connect_error);

        }

   }

 

 

    // prevent cloning class instance

    private function __clone(){}

   

    // perform query

    public function runQuery($query)

    {

        if (is_string($query) AND !empty($query))

        {

            if ((!$this->real_query($query)))

            {

                throw new Exception(‘Error performing query ‘ . $query . ‘ – Error message : ‘ . $this->error);

            }

            return new MySQLi_ResultWrapper($this);

        }

    }

   

    // get insertion ID

    public function getInsertID()

    {

        return $this->insert_id;

    }

   

    // close database connection

    public function __destruct()

    {

        $this->close();

    }

}

At first glance, the logic that drives the above “MySQLiWrapper” class seems to be hard to follow, but the truth is that its inner workings are pretty easy to grasp, trust me. Essentially, all that this class does is perform some MySQL-related tasks, such as establishing a connection to the server, executing queries, and so on. Naturally, if you’re like me, then you’ll have focused your attention on the “runQuery()” method. This method, after running a given SQL statement, returns to client code an instance of a class named “MySQLi_ResultWrapper.”

This one is an implementer of the Iterator and Countable SPL interfaces, and its definition is shown below. Take a look, please:

class MySQLi_ResultWrapper extends MySQLi_Result implements Iterator, Countable

{

    private $_pointer = 0;

   

   // fetch row as an object

    public function fetchObject()

    {

        if (!$row = $this->fetch_object())

        {

            return NULL;

        }

        return $row;

    }

   

    // fetch row as an associative array

    public function fetchAssocArray()

    {

        if (!$row = $this->fetch_assoc())

        {

            return NULL;

        }

        return $row;

    }

   

    // fetch row as an enumerated array

    public function fetchNumArray()

    {

        if (!$row = $this->fetch_row())

        {

            return NULL;

        }

        return $row;

    }

   

    // fetch all rows

    public function fetchAll($type = MYSQLI_ASSOC)

    {

        if ($type !== MYSQLI_ASSOC AND $type !== MYSQLI_NUM AND $type !== MYSQLI_BOTH)

        {

            $type = MYSQLI_ASSOC;

        }

        if (!$rows = $this->fetch_all($type))

        {

            return NULL;

        }

        return $rows;  

    }

   

    // get definition information on fields

    public function fetchFieldsInfo()

    {

        if (!$fieldsInfo = $this->fetch_fields())

        {

            throw new Exception(‘No information available for table fields.’);

        }

        return $fieldsInfo;

    }

   

    // get definition information on next field

    public function fetchFieldInfo()

    {

        if (!$fieldInfo = $this->fetch_field())

        {

            throw new Exception(‘No information available for current table field.’);   

        }

        return $fieldInfo;

    }

   

    // move pointer in result set to specified offset

    public function movePointer($offset)

    {

        $offset = abs((int)$offset);

        $limit = $this->num_rows – 1;

        if ($limit <= 0 OR $offset > $limit)

        {

            return NULL;

        }

        unset($limit);

        return $this->data_seek($offset);

    }

   

    // count rows in result set (implementation required by ‘count()’ method in Countable interface)

    public function count()

    {

        return $this->num_rows;

    }

      

    // reset result set pointer (implementation required by ‘rewind()’ method in Iterator interface)

    public function rewind()

    {

        $this->_pointer = 0;

        $this->movePointer($this->_pointer);

        return $this; 

    }

   

    // get current row set in result set (implementation required by ‘current()’ method in Iterator interface)

    public function current()

    {

        if (!$this->valid())

        {

            throw new Exception(‘Unable to retrieve current row.’);

        }

        $this->movePointer($this->_pointer);

        return $this->fetchObject();

    }

   

    // get current result set pointer (implementation required by ‘key()’ method in Iterator interface)

    public function key()

    {

        return $this->_pointer;

    }

   

    // move forward result set pointer (implementation required by ‘next()’ method in Iterator interface)

    public function next()

    {

        ++$this->_pointer;

        $this->movePointer($this->_pointer);

        return $this;

    }

   

    // determine if result set pointer is valid or not (implementation required by ‘valid()’ method in Iterator interface)

    public function valid()

    {

        return $this->_pointer < $this->num_rows;

    }

} 

Undoubtedly, implementing the Iterator and Countable interfaces isn’t rocket science, and the previous code fragment demonstrates this pretty clearly. In this case, the “MySQLi_ResultWrapper” class gives a concrete definition to the methods declared by these interfaces, which allows you to treat rows contained in database result sets as simple array elements.

While this is all well and good, it’s fairly easy to make this class even more functional by turning it into an implementer of the ArrayAccess interface. In doing so, it’d be feasible to retrieve specific records in a data set by way of its offset. However, to achieve this the class must include all four of the methods declared by ArrayAccess, which are “offsetExists(),” “offsetGet(),” “offsetSet()” and “offsetUnset()” respectively.

Therefore, in the following segment I’m going to show you how to accomplish this in a painless way. Now, go ahead and read the section to come. It’s only one click away.

{mospagebreak title=Implementing the ArrayAccess SPL interface’s offsetExists() method}

As you might have guessed, implementing the ArrayAccess interface within the previous “MySQLi_ResultWrapper” class is a straightforward process that can be tackled with minor efforts. Basically, the entire process is reduced to defining the methods declared by this interface and nothing else. It’s that simple, really.

However, in this particular case the class will provide a concrete implementation for only two of these methods, as implementing all of them would be a rather pointless task. Having explained that, here’s the enhanced version of the “MySQLi_ResultWrapper” class, which now conforms partially to the contract imposed by the ArrayAccess interface:  

class MySQLi_ResultWrapper extends MySQLi_Result implements Iterator, ArrayAccess, Countable

{

    private $_pointer = 0;

   

   // fetch row as an object

    public function fetchObject()

    {

        if (!$row = $this->fetch_object())

        {

            return NULL;

        }

        return $row;

    }

   

    // fetch row as an associative array

    public function fetchAssocArray()

    {

        if (!$row = $this->fetch_assoc())

        {

            return NULL;

        }

        return $row;

    }

   

    // fetch row as an enumerated array

    public function fetchNumArray()

    {

        if (!$row = $this->fetch_row())

        {

            return NULL;

        }

        return $row;

    }

   

    // fetch all rows

    public function fetchAll($type = MYSQLI_ASSOC)

    {

        if ($type !== MYSQLI_ASSOC AND $type !== MYSQLI_NUM AND $type !== MYSQLI_BOTH)

        {

            $type = MYSQLI_ASSOC;

        }

        if (!$rows = $this->fetch_all($type))

        {

            return NULL;

        }

        return $rows;  

    }

   

    // get definition information on fields

    public function fetchFieldsInfo()

    {

        if (!$fieldsInfo = $this->fetch_fields())

        {

            throw new Exception(‘No information available for table fields.’);

        }

        return $fieldsInfo;

    }

   

    // get definition information on next field

    public function fetchFieldInfo()

    {

        if (!$fieldInfo = $this->fetch_field())

        {

            throw new Exception(‘No information available for current table field.’);   

        }

        return $fieldInfo;

    }

   

    // move pointer in result set to specified offset

    public function movePointer($offset)

    {

        $offset = abs((int)$offset);

        $limit = $this->num_rows – 1;

        if ($limit <= 0 OR $offset > $limit)

        {

            return NULL;

        }

        unset($limit);

        return $this->data_seek($offset);

    }

   

    // count rows in result set (implementation required by ‘count()’ method in Countable interface)

    public function count()

    {

        return $this->num_rows;

    }

   

    // reset result set pointer (implementation required by ‘rewind()’ method in Iterator interface)

    public function rewind()

    {

        $this->_pointer = 0;

        $this->movePointer($this->_pointer);

        return $this; 

    }

   

    // get current row set in result set (implementation required by ‘current()’ method in Iterator interface)

    public function current()

    {

        if (!$this->valid())

        {

            throw new Exception(‘Unable to retrieve current row.’);

        }

        $this->movePointer($this->_pointer);

        return $this->fetchObject();

    }

   

    // get current result set pointer (implementation required by ‘key()’ method in Iterator interface)

    public function key()

    {

        return $this->_pointer;

    }

   

    // move forward result set pointer (implementation required by ‘next()’ method in Iterator interface)

    public function next()

    {

        ++$this->_pointer;

        $this->movePointer($this->_pointer);

        return $this;

    }

   

    // determine if result set pointer is valid or not (implementation required by ‘valid()’ method in Iterator interface)

    public function valid()

    {

        return $this->_pointer < $this->num_rows;

    }

   

    // determine if the given offset exists (implementation required by ‘offsetExists()’ method in ArrayAccess interface)

    public function offsetExists($offset)

    {

        $this->movePointer($offset);

        $row = $this->fetchObject();

        return isset($row);

    }

   

    // free up result set

    public function __destruct()

    {

        $this->close();

    }

}

At this stage, the “MySQLi_ResultWrapper” class only implements one of the four methods declared by the ArrayAccess interface, which turns out to be “offsetExists().” As its name suggests, this method can be used for determining whether or not a specified offset within a given result set is valid, and naturally the resulting Boolean value of this process is returned to client code.

Of course, any attempt to test the class in its current state will raise a fatal error from the PHP engine, so I recommend that you not try to do so for the moment. I’d like to finish this tutorial, though, by adding to the “MySQLi_ResultWrapper” class another method of the ArrayAccess interface — the one called “offsetSet().”

However, this process will be discussed in detail in the last section. Therefore, to get there click on the link below and keep reading.    

{mospagebreak title=Adding an offsetSet() method}

The last topic that I plan to cover in this tutorial will be the inclusion of an “offsetSet()” method within the “MySQLi_ResultWrapper” class. As you’ll recall, this method is one of the four declared by the ArrayAccess interface, so this process is mandatory. In accordance with this, now the source code of the pertinent class will look like this:  

 

class MySQLi_ResultWrapper extends MySQLi_Result implements Iterator, ArrayAccess, Countable

{

    private $_pointer = 0;

   

   // fetch row as an object

    public function fetchObject()

    {

        if (!$row = $this->fetch_object())

        {

            return NULL;

        }

        return $row;

    }

   

    // fetch row as an associative array

    public function fetchAssocArray()

    {

        if (!$row = $this->fetch_assoc())

        {

            return NULL;

        }

        return $row;

    }

   

    // fetch row as an enumerated array

    public function fetchNumArray()

    {

        if (!$row = $this->fetch_row())

        {

            return NULL;

        }

        return $row;

    }

   

    // fetch all rows

    public function fetchAll($type = MYSQLI_ASSOC)

    {

        if ($type !== MYSQLI_ASSOC AND $type !== MYSQLI_NUM AND $type !== MYSQLI_BOTH)

        {

            $type = MYSQLI_ASSOC;

        }

        if (!$rows = $this->fetch_all($type))

        {

            return NULL;

        }

        return $rows;  

    }

   

    // get definition information on fields

    public function fetchFieldsInfo()

    {

        if (!$fieldsInfo = $this->fetch_fields())

        {

            throw new Exception(‘No information available for table fields.’);

        }

        return $fieldsInfo;

    }

   

    // get definition information on next field

    public function fetchFieldInfo()

    {

        if (!$fieldInfo = $this->fetch_field())

        {

            throw new Exception(‘No information available for current table field.’);   

        }

        return $fieldInfo;

    }

   

    // move pointer in result set to specified offset

    public function movePointer($offset)

    {

        $offset = abs((int)$offset);

        $limit = $this->num_rows – 1;

        if ($limit <= 0 OR $offset > $limit)

        {

            return NULL;

        }

        unset($limit);

        return $this->data_seek($offset);

    }

   

    // count rows in result set (implementation required by ‘count()’ method in Countable interface)

    public function count()

    {

        return $this->num_rows;

    }

   

    // reset result set pointer (implementation required by ‘rewind()’ method in Iterator interface)

    public function rewind()

    {

        $this->_pointer = 0;

        $this->movePointer($this->_pointer);

        return $this; 

    }

   

    // get current row set in result set (implementation required by ‘current()’ method in Iterator interface)

    public function current()

    {

        if (!$this->valid())

        {

            throw new Exception(‘Unable to retrieve current row.’);

        }

        $this->movePointer($this->_pointer);

        return $this->fetchObject();

    }

   

    // get current result set pointer (implementation required by ‘key()’ method in Iterator interface)

    public function key()

    {

        return $this->_pointer;

    }

   

    // move forward result set pointer (implementation required by ‘next()’ method in Iterator interface)

    public function next()

    {

        ++$this->_pointer;

        $this->movePointer($this->_pointer);

        return $this;

    }

   

    // determine if result set pointer is valid or not (implementation required by ‘valid()’ method in Iterator interface)

    public function valid()

    {

        return $this->_pointer < $this->num_rows;

    }

   

    // determine if the given offset exists (implementation required by ‘offsetExists()’ method in ArrayAccess interface)

    public function offsetExists($offset)

    {

        $this->movePointer($offset);

        $row = $this->fetchObject();

        return isset($row);

    }

   

    // not implemented (required by ‘offsetSet()’ method in ArrayAccess interface)

    public function offsetSet($offset, $value){}

   

    // free up result set

    public function __destruct()

    {

        $this->close();

    }

}

Done. At this point, the “MySQLi_ResultWrapper” class defines two methods of the ArrayAccess interface, even though only one of them has a concrete implementation. Again, at the risk of being repetitive, if you test the class in its current version, you’ll get an error from the PHP interpreter. Please be patient for now, as the remaining interface methods will be added to the class in the course of the next article. Meanwhile, feel free to edit all of the code samples shown in this tutorial, which will sharpen your skills when implementing the Iterator, Countable and ArrayAccess PHP 5 interfaces.

Final thoughts

That’s all for now. In this part of the series, I showed how to partially implement the methods declared by the ArrayAccess SPL interface within a sample class that manipulates MySQL result sets. At this point, the class only defines two of the four methods required by the interface, but it’s mandatory to give a definition to the remaining ones. This issue will be properly addressed in the next part of the series, so you can’t miss it!

[gp-comments width="770" linklove="off" ]

chat