User-defined Interfaces in PHP 5: Introduction to Core Concepts - Making the Round Trip: defining the “MySQLCache” class (
Page 4 of 5 )
Now that you know how to make a class implement an interface, I’m going to define the second class, “MySQLCache”, which caches MySQL results based on a time expiry trigger. Its source code is as following:
// class MySQLCache
class MySQLCache implements DeSerializer{
private $host; // MySQL host
private $user; // MySQL user
private $password; // MySQL password
private $database; // MySQL database
private $query; // SQL query
private $cacheFile; // cache file
private $expiry; // expire time
public function __construct($parameters=array()){
if(count($parameters)<7){
throw new Exception('Invalid number of parameters');
}
foreach($parameters as $parameter=>$value){
if(!$parameter||!$value){
throw new Exception('Invalid connection
parameters');
}
$this->{$parameter}=$value;
}
}
// determine if cached data is valid
private function isCacheValid(){
if(file_exists($this->cacheFile)&&filemtime($this-
>cacheFile)>(time()-$this->expiry)){
return true;
}
return false;
}
private function readMySQL(){
$this->connectDB();
return $this->query();
}
private function connectDB(){
if(!$this->conID=mysql_connect($this->host,$this-
>user,$this->password,$this->database)){
throw new Exception('Error connecting to MySQL');
}
if(!mysql_select_db($this->database,$this->conID)){
throw new Exception('Error selecting database');
}
}
private function query(){
if(!$this->result=mysql_query($this->query)){
throw new Exception('Error running query '.$this-
>query);
}
while($row=mysql_fetch_array($this->result)){
$data[]=$row;
}
$this->data=$data;
$this->data=$this->getSerializedData();
$this->writeCache();
return $data;
}
private function writeCache(){
// write cache file
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');
}
// write serialized data to cache file
if(!fwrite($fp,$this->data)){
throw new Exception('Error writing to cache file');
}
// unlock cache file
flock($fp,LOCK_UN);
fclose($fp);
}
// read cache file
private function readCache(){
if(!$this->data=file_get_contents($this->cacheFile)){
throw new Exception('Error reading cache file');
}
// return cached data
return $this->getUnserializedData();
}
// unserialize data
public function getUnserializedData(){
return unserialize($this->data);
}
// serialize data
public function getSerializedData(){
return serialize($this->data);
}
// read data either from MySQL or cache file
public function getData(){
if(!$this->isCacheValid()){
return $this->readMySQL();
}
else{
return $this->readCache();
}
}
}
Despite the apparently-complicated look of the class, its rationale is fairly simple. Essentially, the tasks performed by the class can be subsumed in the following sequence: first, a MySQL result set is obtained through the “query()” method, once a successful connection has been established. Then, the result set is converted to an array and serialized by the “getSerializedData()” method. Finally, the serialized data is written to a specified cache file, which will be evaluated by the “isCacheValid()” method, in order to force a new cache generation when a given time expiry has been reached.
As long as the cache is considered valid, data will be read from the given cache file. Otherwise, a new result set will be retrieved by running a SELECT statement and returned to the application as an array for further processing.
Since the class’ logic is fairly understandable, I won’t spend a long time explaining its functionality. However I’d like to strongly point out the implementation of the “DeSerializer” interface within the class. Notice that again the “getSerializedData()” and “getUnserializedData()” are specifically defined within the class, to perform the serialize-unserialize sequence on the data. Although these methods basically perform the same tasks exposed within the first class “PostSaver”, they’re implemented in a different way, as you can appreciate through the lines below:
// unserialize data
public function getUnserializedData(){
return unserialize($this->data);
}
// serialize data
public function getSerializedData(){
return serialize($this->data);
}
This condition is telling us that, despite the fact that both classes implement the same interface, the corresponding methods are defined differently. Therefore, the generic functionality defined through the interface is specifically implemented in different ways at class level. With reference to the example, there are two types of objects with nothing in common that use the same methods declared in the interface.
The example shows clearly the differences in using subclasses derived from the same abstract parent class, due to the mere fact that each object belongs to a different family type. Otherwise, when working with a base parent class, child objects would be of the same type. In either case, as an implementer, it’s important that you know how to spot the difference.
At this point, I have demonstrated the functionality of user-defined interfaces. However, in order to reaffirm the concepts that you just learned, I’ll write a couple of functional examples that demonstrate the usage of the previous classes. Thus, let’s get rid of the boring details and jump straight into the examples.