HomePHP Page 4 - Caching Result Sets in PHP: Implementing the Caching System in PHP 5
Completing the caching application: assembling the relevant classes - PHP
Welcome to the final part of the series “Caching Result Sets in PHP.” Hopefully, the last chapter of this tutorial will help you to put together all of the classes developed in the previous part, and demonstrate how the complete caching system can be implemented in a PHP 5 controlled environment.
At this point, we’re armed with the knowledge and the classes needed to implement successfully the complete caching system. To begin with, let’s first list the source code for each one of the reviewed classes and then set up an illustrative example. Here is the first required class, “MySQL”, which, as you probably remember, looked like this:
// class MySQL
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); } }
Then, the next piece of the puzzle, the “Result” class:
//class Result
class Result { private $mysql; // instance of MySQL object private $result; // result set 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'); } } }
Next, let’s list the code for the “arrayProcessor” class:
// class arrayProcessor
class arrayProcessor{ private $data; // data array for processing // constructor public function __construct($data=array()){ if(!is_array($data)){ throw new Exception('Invalid data array'); } $this->data=$data; } // get first array element public function getFirstElement(){ if(!$data=reset($this->data)){ throw new Exception('Error fetching first array element'); } return $data; } // get current array element public function getCurrentElement(){ if(!$data=current($this->data)){ return false; } return $data; } // get last array element public function getLastElement(){ if(!$data=end($this->data)){ throw new Exception('Error fetching last array element'); } return $data; } // get previous array element public function getPreviousElement(){ if(!$data=prev($this->data)){ return false; } return $data; } // get next array element public function getNextElement(){ if(!$data=next($this->data)){ return false; } return $data; } // reverse array elements function reverseElements($prekeys=true){ if(!$data=array_reverse($this- >data,$prekeys)){ throw new Exception('Error reversing array elements'); } return $data; } // sort array elements public function sortElements(){ if(!sort($this->data)){ throw new Exception('Error sorting array elements'); } return $this->data; } // get range of array elements public function getRange($offset,$length){ if(!$data=array_slice($this->data,intval ($offset),intval($length))){ throw new Exception('Error fetching range of elements'); } return $data; } // get random array element public function getRandomElement(){ if(!shuffle($this->data)){ throw new Exception('Error shuffling array elements'); } return $this->data[0]; } // search element in array public function searchElement($keyword){ return array_search($keyword,$this- >data); } // count array elements public function countElements(){ if(!$data=count($this->data)){ throw new Exception('Error counting array elements'); } return $data; } }
Finally, the core piece of the caching system, the “Cache” class:
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); } }
Okay, I think that nothing was left out here. Now, we’re ready to set up an example. Thus, join me in the next section, to see how easily the caching system is put to work.