Caching Result Sets in PHP: The Barebones of a Caching Class - The object-oriented solution: developing a result set caching class (
Page 3 of 5 )
Prior to developing the caching class, we need to specify some important prerequisites. First, the class will implement a time expiry-based caching trigger. However, this is not a big limitation, since a different caching trigger might be developed without making significant changes to the source code. This will be left as a possible additional feature.
Second, the class will aggregate a MySQL abstraction object, for having database connectivity within its scope, so if you’re not familiar with the concept of aggregation, feel free to visit Object Interaction in PHP Introduction to
Aggregation part 1, where I’ve written an article series that discusses the topic in detail.
Finally, since the source code is highly portable, in a future article the whole set of classes will be updated to PHP 5, aimed specifically at those developers working with the latest version of PHP.
All right, having stated these few disclaimers, it’s time to look at the result caching class, which, not surprisingly, I’ve called “Cache.” Its definition is as follows:
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 data from MySQL
$this->result=$this->mysql->query($query);
// write data to cache file
$this->data=$this->write();
}
else {
// read data 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 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;
}
}
At first glance, and looking at the data member declaration, we can see that the “Cache” class aggregates a MySQL object and a “Result” object, where the first one is directly passed to the constructor. So, if you might want to see the complete code for each class involved, don’t feel concerned about this. We’ll see in turn, the proper definition for all of the additional classes that we’re working with.
For the moment, let’s focus our attention on the code for the “Cache” class, which will be explained in detail over the next few lines.