HomePHP Page 2 - Caching Result Sets in PHP: Object Interaction Within a Caching System
The first link in the caching process: looking at the “Cache” class - PHP
In this article, we work directly with a standalone caching class, showing how it interacts with other objects. We will work with aggregation and composition to achieve our goals, which include implementing a time expiry based caching mechanism.
In order to get a better understanding of the interaction process between classes, let’s remind ourselves of how the “Cache” class looks, since it’s the first relevant structure within the caching system. To refresh our memories, here is the complete definition for this class:
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()){ $this->result=$this->mysql- >query($query); $this->data=$this->write(); } else { $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 cache row function fetchRow(){ if(!$row=current($this->data)){ return false; } next($this->data); return $row; } // fetch all cache rows function fetchAll(){ if(count($this->data)<1){ $this->mysql->isError('Error accessing cache data'); } return $this->data; } // count cache rows function countRows(){ if(!$rows=count($this->data)){ $this->mysql->isError('Error counting cache rows'); } return $rows; } }
So far, so good. Since the class has been explained in detail over the third part of the series, I won’t stop long on it. Let’s take a brief look at some of its most important features, before jumping directly into the source code for the other classes.
As we’ve seen before, the “Cache” class allows you to store an entire result set in a cache file for a given time period. Once the expiry has been reached, a new query is run for getting “fresh” data, and then the cache generation is carried out again. As long as the cached data is valid, it will be read from the file, being available for some kind of further processing.
Asides from the regular methods to handle programmatically the cached data, the class exposes some additional methods for fetching either single rows or multiple rows, as well as the ability to count rows.
Indeed, these features don’t present any difficulties. However, there are some interesting things within the class definition. Note that database connectivity tasks, query execution, and error handling are delegated to different classes. This implicitly means that there must be other objects that are passed to the “Cache” class for carrying out these specific operations.
Essentially, we can see that all of the database work is done by a MySQL object, which is passed to the class by its constructor. Therefore, it’s time to take a look at the MySQL abstraction class, responsible for connecting to MySQL, selecting databases and running queries.