Implementing More Methods of the ArrayAccess SPL Interface

In this fifth part of a six-part series on the Iterator, Countable and ArrayAccess SPL interfaces, I implement the rest of the methods declared by the ArrayAccess SPL interface within the “MySQLi_ResultWrapper” class that you saw in a previous part. Once this process is completed, the class will be able to treat records in database result sets as if they were plain array elements.

If you’re a PHP developer who frequently builds object-oriented web applications, then it’s possible that you’ve already worked with user-defined interfaces. PHP 5 offers strong support for them, among the numerous features that make up its highly-enhanced object model.

However, the support for interfaces doesn’t stop in the so-called “userland.” The language comes with a decent number of native interfaces for building implementing classes that perform all sorts of clever tasks, such as overloading arrays, iterating recursively over directories in the file system, and so forth.

Included with these native interfaces that are part of the Standard PHP Library (SPL), there are a few that deserve an in-depth analysis. They’re very popular with many PHP programmers, especially for developing flexible database access layers. In this particular case, I’m talking about the Iterator, Countable and ArrayAcccess native interface. When used properly, they permit developers to create implementing classes that not only exploit the intrinsic benefits of Polymorphism, but allow them to overload arrays in a pretty painless fashion.

Of course, if you’ve been a loyal reader and have already read all of the articles that precede this one, then at this point you have a clear idea of how to implement the aforementioned interfaces within a real-world class. In those tutorials I started developing one that was tasked with manipulating MySQL result sets via a simple API. The class in question implemented the methods defined by the Iterator and Countable SPL interfaces respectively, which gave it the ability to traverse database record sets by using a plain “foreach” construct, as well as count the number of rows contained in those sets.

In addition, two of the methods declared by the ArrayAccess interface were included in the definition of this sample MySQL abstraction class, in this way extending its existing functionality even further. As you possibly know, though, any class implementing a given interface must include (and in most cases, implement) all of the methods that the interfaces define.

In order to meet this requirement, in this fifth chapter of the series I’m going to finish implementing the remaining methods of the ArrayAccess interface within the MySQL abstraction class built previously. Now, let’s learn more about the ArrayAccess interface. Let’s go!

{mospagebreak title=Review: the MySQLiWrapper and MySQLi_ResultWrapper classes}

Since my goal in this article is to make the MySQL handling class built in the prior chapter a full implementer of the ArrayAccess SPL interface, first I want to review the definition of this class, along with the one that connects to the database server and runs hard-coded SQL statements.

Having explained that, here’s the source code of the sample class that performs the aforementioned tasks:

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();

    }

}

Since at this point the inner workings of the previous “MySQLiWrapper” class should be pretty familiar to you, we’ll move forward and take a close look at the definition of the following class. As I explained in the introduction, aside from implementing the Iterator and Countable PHP interfaces, this class includes two of the methods declared by ArrayAcess. Here’s how this other sample class has been defined:

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();

    }

}

So far, there’s nothing hard to understand with reference to the way the “MySQLi_ResultWrapper” class has been defined, right? As I said before, this class is only a partial implementer of the ArrayAccess interface, as it defines two of the four methods of the interface, “offsetExists()” and “offsetSet()” respectively.

For obvious reasons, in its current state the class isn’t fully functional, since it must include all of the methods defined by ArrayAccess. Therefore, to suit this requirement, in the following section I’m going to add to the class another method of the corresponding interface, called “offsetUnset(),” which in this case won’t have any explicit implementation.

Now, click on the link below and read the next segment.

{mospagebreak title=Defining the offsetUnset() method}

Due to the fact that the sample “MySQLi_ResultWrapper” class at this point  defines only two methods of the four declared by the ArrayAccess interface, we must add to the class another of these required methods. In this case, the method will be “offsetUnset(),” which won’t be specifically implemented.

After including this brand new method, the source code of the class will be as follows:

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){}

   

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

    public function offsetUnset($offset){}

   

    // free up result set

    public function __destruct()

    {

        $this->close();

    }

}

As you can see, adding to the above “MySQLi_ResultWrapper” class the “offsetUnset()” method doesn’t change its behavior, since this method has no  concrete implementation, but of course you may want to change this on your own to fit your personal needs.

However, we’re still not done with this class. The ArrayAccess interface requires any implementer of it to include another method called “offsetGet().” Thus, in the section to come I’m going to add this last method to the class, which as you’ll see will be concretely implemented.   

To see how the “MySQLi_ResultWrapper” class will look after implementing this final interface method, click on the link below and read the next few lines.

{mospagebreak title=Finish defining the methods of the ArrayAccess interface}

As I stated in the section that you just read, it’s mandatory to provide the “MySQLi_ResultWrapper” class with an “offsetGet()” method to confirm the contract required by the ArrayAccess interface. In consonance with this, below I listed the finished version of the class, this time including the method in question.

Look at the following code sample:

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);

    }

   

    // get row according to given offset (implementation required by ‘offsetExists()’ method in ArrayAccess interface)

    public function offsetGet($offset)

    {

        $this->_pointer = abs((int)$offset);

        return $this->current();

    }

   

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

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

   

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

    public function offsetUnset($offset){}

   

    // free up result set

    public function __destruct()

    {

        $this->close();

    }

}

Mission accomplished. At this stage, the “MySQLi_ResultWrapper” class implements the required “offsetGet()” method, which gives it the ability to access a record within a result set by specifying only an offset. With this last addition, the class is finally ready to be properly tested, without having to get (at least in theory) an ugly complaint from the PHP engine.

However, the full details of this testing process will be covered in the following tutorial.

Final thoughts

In this fifth episode of the series, I finished implementing the rest of the methods declared by the ArrayAccess SPL interface within the “MySQLi_ResultWrapper” class that you saw previously. With this process completed, the class can treat records in database result sets as if they were plain array elements.

Now that this sample class is ready to be tested, in the last article I’m going to code a few illustrative examples that will put the class in action, so you can see its actual functionality.

Don’t miss the final chapter of the series!

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

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort