Caching Result Sets in PHP: Implementing the Caching System in PHP 5

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.

As you probably remember, the developed caching application is essentially composed of several classes that have a delineated range of operations, instead of being structured as a huge wealth of packaged methods and properties that don’t keep a strong relationship with each other.

This methodology for developing a complex system, by splitting the whole application into many functional classes, definitely presents greater flexibility and portability, since each class acts like a programming entity capable of receiving and transferring processed data, and plugging itself into another class.

Based on some of the key concepts described above, I’ve built a result set caching class that aggregates a MySQL abstraction class, and utilizes an array processor (known commonly as an array Iterator) to carry out all of the array operations.

Of course, the underlying theory would be rather useless without writing the code to be used in real environments. Therefore, let’s get ready to tackle the final round and build the complete caching application. It’s going to be exciting!

{mospagebreak title=Caching result sets in an PHP 5 scenario: a detailed look at the “Cache” class}

To complete the process of developing the caching system, we have to analyze in detail the core “Cache” class and understand properly its logic. Although this class was already reviewed in the previous part of the series, it’s useful to refresh our memory and take a look at its definition. That way, it will be easier to explain how the array processor class fits into the programming schema.

The list for the “Cache” class is the following:

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);
            }
}

As you’ve seen before, the class essentially handles the logic for reading a result set either from MySQL or from the specified cache file, based on a time expiry caching policy. Due to the fact that the class hides the internal processing behind the “query()” method, it’s possible to do a little refactoring and implement a different cache trigger or even a combination of triggers.

Asides from handling the caching trigger, the class exposes several additional methods for single and multiple row fetching, as well as for counting rows and returning a range of them. It’s easy to see that each of the methods that performs array operations, uses the methods provided by the array processor object.

This is best understood by taking a look at the following line, within the “query()” method:

// create a new instance of arrayProcessor object
$this->arrproc=new arrayProcessor($this->data);

Whenever this method is invoked, it instantiates an object from the “arrayProcessor” class, which is directly fed with the array obtained from the cache file, in case you have a valid caching period, or from the query resource.

Notice that the array processor object’s methods are used to fetch or count rows. However, it’s fairly easy to add more methods for more complex result set manipulation. For instance, suppose that we need to return a randomly selected row from a result set. This would be quickly done by using a method of the array processor that picks up a random element. Indeed, expanding the “Cache” class’ functionality would be a pretty straightforward process.

Since the “Cache” class relies heavily on the array processor object, it’s necessary to look at its structure. Doing so, the concepts explained above will be better grasped.

{mospagebreak title=Traversing an array structure: developing an array Iterator}

Building an array Iterator is not a complex task at all. Specifically, the PHP5 SPL (Standard PHP Library) offers a new set of interfaces for implementing Iterators very easily (Iterator, ArrayIterator, and so on), so if you want to go deeper into the topic, visit the PHP site for detailed information.

In this case, I won’t use these interfaces for developing an array iterator, since they’re available only in PHP 5. For those developers using PHP 4, coding such a programming structure can be a didactical experience, before deciding to migrate to PHP’s latest version. However, if you’ve crossed the bridge to PHP 5, I strongly recommend using these existing interfaces.

Despite the fact that some of the common design patterns applied in software engineering are considered to be within the category of “advanced concepts,” having a decent knowledge of their theory and simple implementation in practical situations, helps you to know how to apply them in real conditions. Implementing the Iterator pattern to traverse a simple array structure may be as simple as this:

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;
            }
}

Although the code seems to be rather long, it’s quite simple to understand. It simply exposes the methods needed for performing different operations on an array structure. The class presents different methods for getting the current array element, or moving the array pointer back and forth. What’s more, we’re able to count, search and reverse array elements.

Since we’ve analyzed the logic of the “arrayProcessor” class, now the reason for using its methods within the “Cache” class should become clear. Because results sets are manipulated as associative arrays, it’s a matter of common sense to utilize an array Iterator for handling them. This is a simple topic, right?

All right, now we have seen in detail how these two classes establish a strong interaction and play a relevant role within the caching application.

I know that it’s easy to say that using smaller and portable classes to build a bigger caching system leads to easier and faster implementation, but if you find yourself writing a large class that does “everything,” reconsider your design approach and start thinking of applications as separated blocks of reusable code that can be easily plugged into each other.

Keeping in mind these guidelines within application development, it’s time to put all of the classes together and assemble the whole caching system. Just click to the next page to learn how this is done.

{mospagebreak title=Completing the caching application: assembling the relevant classes}

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.

{mospagebreak title=A practical approximation: putting the caching application to work}

As you might guess, since each class is hiding all of the internal processing behind its interface, coding an example to demonstrate the caching system’s functionality is a breeze. The lines below show a simple way of caching result sets:

// include the class files (notice that class files can be
included using __autoload for multiple file inclusion)
require_once ‘mysqlclass.php';
require_once ‘resultclass.php';
require_once ‘arrayprocessorclass.php';
require_once ‘cacheclass.php';

try {
// connect to MySQL
            $db=new MySQL(array(‘host’=>’host’,’user’=>’user’,’password’=>’password’,
‘database’=>’databasename’));
            // instantiate a new Cache object and set cache
validity for 24 hours
            $cache=new Cache($db,86400);
            // get result set
            $cache->query(‘SELECT * FROM users’);
            // fetch one row at time
            while($row=$cache->fetchRow()){
                        echo $row['firstname'].'<br />';
            }
            // display total number of rows
            echo ‘Total number of rows ‘.$cache->countRows();
            // fetch all rows
            $rows=$cache->fetchAll();
            // display data
            foreach($rows as $data){
                        echo $data['firstname'].$data
['lastname'].'<br />';
            }
            // fetch ten rows
            $rows=$cache->fetchRange(0,10);
            // display data
            foreach($rows as $data){
                        echo $data['firstname'].$data
['lastname'].'<br />';
            }
}
// catch thrown exceptions
catch(Exception $e){
echo $e->getMessage();
            exit();
}

As you can see, the example speaks for itself. To demonstrate the functionality of the caching system, I’ve explicitly used the available methods for fetching result sets. Doing so, the usage of them is clearly demonstrated, in this case by displaying the data from a “users” database table. However, it’s feasible to use one method for data visualization and another for different processing (i.e. sending by email cached data).

Another possibility would be to use the “fetchRange()” method for displaying paginated data. As usual, feel free to add your own application for each explained method. Once the required functionality is available, possibilities are really numerous.

Conclusion

Finally, the journey has ended. Through this series, we’ve explored together the many options that PHP offers for caching result sets. Starting out from the creation of a simple procedural script, to the development of a more advanced object-oriented solution, hopefully the whole experience has been rich both in theory and practice.

Result set caching is a powerful technique that can be implemented to accelerate many websites that deliver quite static content, and PHP exposes a set of nice features to take advantage of caching solutions. Reduced server overload and limited query execution translate directly into faster and more efficient web applications. From a developer’s point of view, there is much to gain in terms of better performance and cost-efficient website acceleration.

[gp-comments width="770" linklove="off" ]

chat sex hikayeleri Ensest hikaye