HomePHP Page 2 - Caching Result Sets in PHP: Porting the Code to PHP 5
One step prior to migrating: caching result sets with PHP 4 - PHP
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.
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'].' '.$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.