Caching Result Sets in PHP: Porting the Code to PHP 5

In this part of the series, you will see in detail an updated version of each class that composes the caching system, for a correct implementation in PHP 5. Also, the “Cache” class will be modified to work with an array processor class that handles array operations. It should help refresh your memory of techniques related to object-oriented programming.

Welcome to the fifth part of the series “Caching result sets in PHP.” If you’ve been a patient reader, and followed the previous articles in this series, then you probably have a decent grounding in the different methods of developing a result set caching system, without the need to scratch your head looking for more complex solutions.

As a matter of fact, storing a complete result set in a cache file for faster data retrieval and reduced server overload is an extremely versatile technique that brings to developers a greater level of flexibility, since it allows single or multiple post processing of the data, while keeping the inherent benefits of having a caching mechanism working transparently in the backend of a website.

Many websites that use a database as a direct way to store contents that don’t change very often (naturally static), such as a product catalog, or a list of articles, to name a few cases, can significantly improve their performance by implementing a caching solution.

Although the concept behind data caching certainly is not new, as modern developing techniques have emerged and found their place in the market, several approaches may be successfully taken.

Particularly, when working with PHP, where either a procedural programming or an object-oriented approach is taken for solving many development issues, the subject is even richer with possibilities. Keeping in mind this concept, after going through the previous articles, we’ve developed a couple of procedural scripts that might be useful for implementing in small applications.

However, we’ve not forgotten all the benefits of developing some classes that can be integrated within an expandable caching system. Indeed, if we step back for a while to the previous article, we’ve built a time expiry triggered caching class, which allows fast implementation in web applications.

As I mentioned previously, the whole set of developed classes that worked in PHP 4, need to be updated to PHP 5, in order to take advantage of the powerful Object Model that was implemented in this version. So, let’s not waste more time in preliminaries and jump straight into the code. You’ll have a good time reading, I promise.

{mospagebreak title=One step prior to migrating: caching result sets with PHP 4}

In order to take a quick look at the classes written in PHP 4, let’s first see their definitions and set up a short example. As you probably remember, I’ve developed a MySQL wrapping class that handles all of the operations for connecting to the server, running queries and obtaining result sets. Its source code was the following:

class MySQL{
            var $conId; // connection identifier
            var $host; // MySQL host
            var $user; // MySQL username
            var $password; // MySQL password
            var $database; // MySQL database
            // constructor
            function MySQL($options=array()){
                        // validate incoming parameters
                        if(count($options)>0){
                                   foreach($options as
$parameter=>$value){
                                               (!empty($value))?
$this->{$parameter}=$value:$this->isError(‘Invalid parameter
‘.$parameter);
                                   }
                                   // connect to MySQL
                                   $this->connectDB();
                        }
                        else {
                                   $this->isError(‘No connection
parameters were provided’);
                        }
            }
            // connect to MYSQL server and select database
            function connectDB(){
                        if(!$this->conId=mysql_connect($this-
>host,$this->user,$this->password)){
                                    $this->isError(‘Error
connecting to the server’);
                        }
                        if(!mysql_select_db($this-
>database,$this->conId)){
                                    $this->isError(‘Error
selecting database’);
                        }
            }
            // perform query
            function query($query){
                        if(!$this->result=mysql_query
($query,$this->conId)){
                                   $this->isError(‘Error
performing query ‘.$query);
                        }
                        // return new Result object
                        return new Result($this,$this->result);
            }
            // display errors
            function isError($errorMsg){
                       trigger_error($errorMsg.’ ‘.mysql_error
());
                       exit();
            }
}

 

Then, the next class involved to make the caching system work was the “Result” class, which is aimed specifically at managing MySQL result sets. Its corresponding source code was defined in the following way:

class Result{
            var $mysql; // instance of MySQL object
            var $result; // result set
            // constructor
            function Result(&$mysql,$result){
                        $this->mysql=&$mysql;
                        $this->result=$result;
            }
            // fetch row
            function fetchRow(){
                        return mysql_fetch_array($this-
>result,MYSQL_ASSOC);
            }
            // count rows
            function countRows(){
                        if(!$rows=mysql_num_rows($this->result)){
                                   $this->mysql->isError(‘Error
counting rows’);
                        }
                        return $rows;
            }
            // count affected rows
            function countAffectedRows(){
                        if(!$rows=mysql_affected_rows($this-
>mysql->conId)){
                                   $this->mysql->isError(‘Error
counting affected rows’);
                        }
                        return $rows;
            }
            // get ID from last inserted row
            function getInsertID(){
                        if(!$id=mysql_insert_id($this->mysql-
>conId)){
                                   $this->mysql->isError(‘Error
getting ID’);
                        }
                        return $id;
            }
            // seek row
            function seekRow($row=0){
                        if(!mysql_data_seek($this->result,$row)){
                                   $this->mysql->isError(‘Error
seeking data’);
                        }
            }
}

Finally, the core class for handling the logic of the caching system was the already familiar “Cache” class. It was defined like this:

class Cache{
            var $mysql;  // instance of MySQL object
            var $result; // instance of Result object
            var $expiry; // cache expire time in seconds
            var $cacheFile; // cache file
            var $data; // result set array
            // constructor
            function Cache
(&$mysql,$expiry=86400,$cacheFile=’default_cache.txt’){
                        $this->mysql=&$mysql;
                        (is_int($expiry)&&$expiry>0)?$this-
>expiry=$expiry:$this->mysql->isError(‘Expire time must be a
positive integer’);
                        $this->cacheFile=$cacheFile;
                        $this->data=array();
            }
            // if cache is valid, perform query and return a
result set. Otherwise, get results from cache file
            function query($query){
                        // check if query starts with SELECT
                        if(!preg_match(“/^SELECT/”,$query)){
                                   $this->mysql->isError(‘Invalid
query. Must start with SELECT’);
                        }
                        if(!$this->isValid()){
                                   // read from MySQL
                                   $this->result=$this->mysql-
>query($query);
                                   $this->data=$this->write();
                        }
                        else {
                                   // read from cache file
                                   $this->data=$this->read();
                        }
            }
            // write cache file
            function write(){
                        if(!$fp=fopen($this->cacheFile,’w’)){
                                   $this->mysql->isError(‘Error
opening cache file’);
                        }
                        if(!flock($fp,LOCK_EX)){
                                   $this->mysql->isError(‘Unable
to lock cache file’);
                        }
                        while($row=$this->result->fetchRow()){
                                   $content[]=$row;
                        }
                        if(!fwrite($fp,serialize($content))){
                                   $this->mysql->isError(‘Error
writing to cache file’);
                        }
                        flock($fp,LOCK_UN);
                        fclose($fp);
                        unset($fp,$row);
                        return $content;
            }
            // read cache file
            function read(){
                        if(!$content=unserialize
(file_get_contents($this->cacheFile))){
                                   $this->mysql->isError(‘Error
reading from cache file’);
                        }
                        return $content;
            }
            // determine cache validity based on a time expiry
trigger
            function isValid(){
                        if(file_exists($this->cacheFile)
&&filemtime($this->cacheFile)>(time()-$this->expiry)){
                                   return true;
                        }
                        return false;
            }
            // fetch row
            function fetchRow(){
                        if(!$row=current($this->data)){
                                   return false;
                        }
                        next($this->data);
                        return $row;
            }
            // fetch all rows
            function fetchAll(){
                        if(count($this->data)<1){
                                   $this->mysql->isError(‘Error
accessing cache data’);
                        }
                        return $this->data;
            }
            // count rows
            function countRows(){
                        if(!$rows=count($this->data)){
                                   $this->mysql->isError(‘Error
counting cache rows’);
                        }
                        return $rows;
            }
}

Okay, having listed the source code for each class, we’re able to implement an example that shows up the capabilities of the caching system, within an object-oriented scenario. Here it is:

// connect to MySQL
$db=new MySQL(array(‘host’=>’host’,’user’=>’user’,’password’=>’password’,
‘database’=>’databasename’));
// instantiate a new Cache object, valid for 24 hours (time
expiry triggered caching)
$cache=&new Cache($db,86400);
// perform query and store results in cache file
// if cache is not valid, force a new cache generation
$cache->query(‘SELECT * FROM products’);
// loop over rows and display results
while($row=$cache->fetchRow()){
            echo $row['name'].’&nbsp;’.$row
['description'].'<br />';
}
// count rows
echo ‘Total number of products ’ .$cache->countRows();

The above example clearly illustrates how to implement the three previous classes, by putting them together to set up the caching mechanism. In this particular case, I’ve decided to cache the data coming from a  “products” database table during a period of 24 hours. Also, the example shows the usage of the “fetchtRows()” and “countRows()” methods, for displaying data and counting rows respectively.

All right, since we’ve tested the capabilities of this caching solution developed in PHP 4, it’s recommended that we go one step further and explain how a similar system may be programmed in PHP 5. Due to the fact that PHP 5 presents some differences, most of them aimed at improving the Object Model, the advantages and benefits are significant compared to the version implemented in PHP 4.

Therefore, let’s cross the barrier between language versions and update the caching system to PHP 5.

{mospagebreak title=Taking advantage of an improved object model: updating the caching system to PHP 5}

In order to use the features of the new object model present in PHP 5, our first step will consist of updating the MySQL abstraction class. Even when the code is closely similar to the previous version, there are several differences that will be easily clarified by looking at the source code. So, the updated MySQL class looks like this:

class MySQL{
            private $conId; // connection identifier
            private $host; // MySQL host
            private $user; // MySQL username
            private $password; // MySQL password
            private $database; // MySQL database
            // constructor
            public function __construct($options=array()){
                        // validate incoming parameters
                        if(count($options)>0){
                                   foreach($options as
$parameter=>$value){
                                               if(empty($value)){
                                                           throw
new Exception(‘Invalid parameter ‘.$parameter);
                                               }
                                               $this->
{$parameter}=$value;
                                   }
                                   // connect to MySQL
                                   $this->connectDB();
                        }
                        else {
                                   throw new Exception(‘No
connection parameters were provided’);
                        }
            }
            // connect to MySQL server and select database
            private function connectDB(){
                        if(!$this->conId=mysql_connect($this-
>host,$this->user,$this->password)){
                                               throw new
Exception(‘Error connecting to the server’);
                        }
                        if(!mysql_select_db($this-
>database,$this->conId)){
                                    throw new Exception(‘Error
selecting database’);
                        }
            }
            // perform query
            public function query($query){
                        if(!$this->result=mysql_query
($query,$this->conId)){
                                   throw new Exception(‘Error
performing query ‘.$query);
                        }
                        // return new Result object
                        return new Result($this,$this->result);
            }
}

Without a doubt, this class closely resembles the version suitable for PHP 4. However, as you can clearly see, each class member has been defined specifying its visibility. In this case, the only method defined as private is “connectDB()”. The rest of the class methods are publicly accessible from the outside.

The other change introduced to the class is the use of exceptions, within each section of the code where a potential error might occur. See how a new exception is thrown when connecting to the server, selecting a database or running a query? I don’t mean that you must be this paranoid regarding error handling, but it doesn’t hurt at all. Trust me.

So far, this MySQL abstraction class doesn’t present big problems to being understood, so let’s move on and put our efforts into updating the “Result” class.

{mospagebreak title=Yet another modified class: updating the “Result” class}

As you may probably guess, the process for updating the “Result” class is pretty straightforward. Applying the same modifications introduced to the MySQL class, we end up defining the code for this class as follows:

class Result {
            private $mysql; // instance of MySQL object
            private $result; // result set
            // constructor
            public function __construct(&$mysql,$result){
                        $this->mysql=&$mysql;
                        $this->result=$result;
            }
            // fetch row
            public function fetchRow(){
                        return mysql_fetch_array($this-
>result,MYSQL_ASSOC);
            }
            // count rows
            public function countRows(){
                        if(!$rows=mysql_num_rows($this->result)){
                                   throw new Exception(‘Error
counting rows’);
                        }
                        return $rows;
            }
            // count affected rows
            public function countAffectedRows(){
                        if(!$rows=mysql_affected_rows($this-
>mysql->conId)){
                                   throw new Exception(‘Error counting affected rows’);
                        }
                        return $rows;
            }
            // get ID from last inserted row
            public function getInsertID(){
                        if(!$id=mysql_insert_id($this->mysql-
>conId)){
                                   throw new Exception(‘Error
getting ID’);
                        }
                        return $id;
            }
            // seek row
            public function seekRow($row=0){
                        if(!mysql_data_seek($this->result,$row)){
                                   throw new Exception(‘Error
seeking data’);
                        }
            }
}

Once again, I’ve explicitly defined the class’ member visibility, as well as delegated runtime error conditions by throwing exceptions within conflicting sections of the code. That way, either when fetching result rows or moving the result set’s internal pointer, potential errors are easily handled by an exception mechanism.

With the two above classes already updated to PHP 5, it’s time to look at the workhorse of the whole caching system: the “Cache” class. Let’s jump to the next section to find out how this class is properly updated.

{mospagebreak title=The caching system’s core class: updating the “Cache” class}

Since the “Cache” class implements the necessary logic to manipulate data either from a query result or from the cache file, I’ll explain in detail the changes applied to its structure.

As you hopefully remember, the class utilizes an array structure for handling result sets, which are serialized for caching purposes and unserialized for post processing. Considering that the majority of the class operations are carried out on simple arrays, it’d be highly desirable to develop an array iterator class that handles the most common tasks associated with arrays, and then create an instance of it (again, composition plays an important role) within the “Cache” class.

Bearing this concept in mind, let’s have a look at the updated definition for the “Cache” class. It is as follows:

class Cache{
            private $mysql;  // instance of MySQL object
            private $result; // instance of Result object
            private $arrproc; // instance of arrayProcessor
object
            private $expiry; // cache expire time in seconds
            private $cacheFile; // cache file
            private $data; // result data array
            // constructor
            public function __construct
(&$mysql,$expiry=7200,$cacheFile=’default_cache.txt’){
                        $this->mysql=&$mysql;
                        $this->expiry=$expiry;
                        $this->cacheFile=$cacheFile;
                        $this->data=array();
            }
            // if cache is valid, perform query against database.
Otherwise, get results from cache file
            public function query($query){
                        if(!$this->isValid()){
                                   $this->result=&$this->mysql-
>query($query);
                                   // read data from MySQL
                                   $this->data=$this->write();
                        }
                        else {
                                   // read data from cache file
                                   $this->data=$this->read();
                        }
                        // create a new instance of
arrayProcessor object
                        $this->arrproc=new arrayProcessor($this-
>data);
            }
            // write cache file
            private function write(){
                        if(!$fp=fopen($this->cacheFile,’w’)){
                                   throw new Exception(‘Error
opening cache file’);
                        }
                        // lock cache file
                        if(!flock($fp,LOCK_EX)){
                                   throw new Exception(‘Unable to
lock cache file’);
                        }
                        // get result array from query result
                        while($row=$this->result->fetchRow()){
                                   $contents[]=$row;
                        }
                        // write serialized data to cache file
                        if(!fwrite($fp,serialize($contents))){
                                   throw new Exception(‘Error
writing to cache file’);
                        }
                        // unlock cache file
                        flock($fp,LOCK_UN);
                        fclose($fp);
                        unset($fp,$row);
                        // return query result
                        return $contents;
            }
            // read cache file
            private function read(){
                        if(!$contents=unserialize
(file_get_contents($this->cacheFile))){
                                               throw new
Exception(‘Error reading cache file’);
                        }
                        // return cached data
                        return $contents;
            }
            // determine if cached data is valid or not (time
expiry triggered cache)
            private function isValid(){
                        if(file_exists($this->cacheFile)
&&filemtime($this->cacheFile)>(time()-$this->expiry)){
                                   return true;
                        }
                        return false;
            }
            // fetch row
            public function fetchRow(){
                        if(!$row=$this->arrproc-
>getCurrentElement()){
                                   return false;
                        }
                        $this->arrproc->getNextElement();
                        return $row;
            }
            // fetch all rows
            public function fetchAll(){
                        $this->arrproc->countElements();
                        return $this->data;
            }
            // count rows
            public function countRows(){
                        return $this->arrproc->countElements();
            }
            // fetch range of rows
            public function fetchRange($offset,$length){
                        return $this->arrproc->getRange
($offset,$length);
            }
}          

As you can see, now the class includes three objects for doing its work: a reference of a MySQL object, $this->mysql (this object is aggregated), an instance of a “Result” object, that is $this->result, and finally an instance of the array processor $this->arrproc, for performing array operations.

If this sounds rather confusing, let’s explain the logic of some methods. For instance, the “fetchRow()” method now uses the “getCurrentElement()” method that belongs to the array processor for fetching the current element within the result set array. Similarly, this technique is used to count rows, through the “countRows()” method, or even fetch a limited result set using “fetchRange()”. See how we’re using the functionality provided by the array processor? Now, the inclusion of this object should be clear for the correct implementation for the “Cache” class.

By assigning clearly delineated responsibilities to different classes, we’re writing better and more portable code that can be reused in any number of projects.

Of course, there are many things to be reviewed yet, in order to get the big picture about the caching system. Certainly, we need to see in detail the code for the array processor class, as well as how the classes fit into the application. All right, don’t get anxious, because these topics will be fully explained in the next part of the series.

To wrap up

Through the course of this part, we’ve seen in detail the updated version of each class that composes the caching system, for a correct implementation in PHP 5. Also, the “Cache” class has been modified to work with an array processor class that handles array operations. Hopefully, the article has been didactic and useful for refreshing techniques related to object-oriented programming.

With all of the code exposed here, I guess you’ll spend a while adapting it to suit your needs. However, don’t miss the next part, where the complete caching system is put to work. Until then, stay in touch!

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

chat sex hikayeleri Ensest hikaye