HomePHP Page 4 - User-defined Interfaces in PHP 5: Introduction to Core Concepts
Making the Round Trip: defining the “MySQLCache” class - PHP
PHP5 takes users increasingly in the direction of object-oriented programming. The Standard PHP Library (SPL) is a new item that helps developers both with the creation of OOP applications and the maintenance of standardization. The SPL enables developers to work with user-defined interfaces. In this article, the first of a series, Alejandro Gervasio uses examples to introduce you to working with these interfaces, which can keep you from having to reinvent the wheel with each new project.
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.