How to Cache Heavy Operations (such as Database Queries) in PHP?


PHP is a very popular programming language for the web. It is a convenient and powerful, loosely typed programming language that you can pick up and use within a short period of time. It gets so much widely spread and popular maybe thanks to WordPress, a blogging or CMS (Content Management System). Many websites such as facebook are originally based on PHP.

However, PHP is generally considered to be a slow interpreting scripting language, although there are many techniques to accelerate it e.g. Zend Optimiser installed by default. The HHVM helps to compile the PHP at runtime into something like bytecode, which can then be translated into native code, which improves the performance.

Database queries are time consuming, especially for large tables or the tables without proper indexing. Sometimes, such operations will slow down the page loading/processing speed, and then compromise the user experiences. Quite often, such queries take time to complete but the results won’t change that frequently i.e. maybe changed once per day. In this case, it is better to cache these results into files and get them from reading the files next time if found already cached.

MySQL database is good at handling queries with proper indexing. However, when the table gets large, it kinda affects the performance. We can cache whatever computational-intensive operations and store them into files on disks and get them next time by reading directly from the files, which will be a lot faster.

We have provided a PHP class that can be easily used to include in your application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class Cache {
    private $folder = '';
    
    // constructor takes an optional path for storing the cache files
    public function __construct($path = '') {
        $path = trim($path);
        if ((strlen($path) && is_dir($path))) {
            $this->setFolder($path);
        } else {
            $this->setFolder('/tmp/');   // tmp folder if empty
        }
    }
    
    public function getFolder() {
        return $this->folder;  
    }    
    
    public function setFolder($path) {
        $path = trim($path);
        $len = strlen($path);
        if ($len) {
            if (is_dir($path)) {
                if ($path[$len - 1] != '/') {
                  $path .= '/';  // adds a forward slashes at the end
                }  
                $this->folder = $path;
            }
        }
    }
 
    // read from caches, if not found, return null
    public function read($fileName) {
        $fileName = $this->folder.$fileName;
        if (is_file($fileName)) {
            $handle = fopen($fileName, 'rb'); // open as binary
            $variable = fread($handle, filesize($fileName));
            fclose($handle);
            return unserialize($variable);
        } else {
            return null;
        }
    }
     
    // write $variable to $fileName
    public function write($fileName, $variable) {
        $fileName = $this->folder.$fileName;
        $handle = fopen($fileName, 'a');
        fwrite($handle, serialize($variable));
        fclose($handle);
    }
     
    // delete cache entry $fileName
    public function delete($fileName) {
        $fileName = $this->folder.$fileName;
        @unlink($fileName);  // put a @ to avoid file-not-found warning
    }
}
class Cache {
    private $folder = '';
    
    // constructor takes an optional path for storing the cache files
    public function __construct($path = '') {
        $path = trim($path);
        if ((strlen($path) && is_dir($path))) {
            $this->setFolder($path);
        } else {
            $this->setFolder('/tmp/');   // tmp folder if empty
        }
    }
    
    public function getFolder() {
        return $this->folder;  
    }    
    
    public function setFolder($path) {
        $path = trim($path);
        $len = strlen($path);
        if ($len) {
            if (is_dir($path)) {
                if ($path[$len - 1] != '/') {
                  $path .= '/';  // adds a forward slashes at the end
                }  
                $this->folder = $path;
            }
        }
    }
 
    // read from caches, if not found, return null
    public function read($fileName) {
        $fileName = $this->folder.$fileName;
        if (is_file($fileName)) {
            $handle = fopen($fileName, 'rb'); // open as binary
            $variable = fread($handle, filesize($fileName));
            fclose($handle);
            return unserialize($variable);
        } else {
            return null;
        }
    }
     
    // write $variable to $fileName
    public function write($fileName, $variable) {
        $fileName = $this->folder.$fileName;
        $handle = fopen($fileName, 'a');
        fwrite($handle, serialize($variable));
        fclose($handle);
    }
     
    // delete cache entry $fileName
    public function delete($fileName) {
        $fileName = $this->folder.$fileName;
        @unlink($fileName);  // put a @ to avoid file-not-found warning
    }
}

So, the comments are in place. The above class is straightforward and easy to use. For example, I have used the following to cache one query for my website.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    require('class.cache.php');
    $cache = new Cache('cache/1/');
    $entry = md5($url);
    $data = $cache->read($entry);
    $blogtitle = "";
    if ($data !== null) { // found cache
          $blogtitle = $data; // read the data
    } else {
      $query = "select `title` from `blog` where `path`='$url'";
      $result = mysql_query($query, $link);
 
      if (mysql_num_rows($result))
      {
              $blogtitle = mysql_result($result, 0, 0);
      } 
      $cache->write($entry, $blogtitle);  // save the cache for next read 
    }
    require('class.cache.php');
    $cache = new Cache('cache/1/');
    $entry = md5($url);
    $data = $cache->read($entry);
    $blogtitle = "";
    if ($data !== null) { // found cache
          $blogtitle = $data; // read the data
    } else {
  	  $query = "select `title` from `blog` where `path`='$url'";
  	  $result = mysql_query($query, $link);

  	  if (mysql_num_rows($result))
  	  {
  	    	  $blogtitle = mysql_result($result, 0, 0);
  	  } 
  	  $cache->write($entry, $blogtitle);  // save the cache for next read 
    }

So, if we want to clear the cache, we can just simply delete it by:

1
2
3
4
5
    // suppose we run here the query ("update `blog` set `title` = 'test' where `path` = '$url'");
    require('class.cache.php');
    $cache = new Cache('cache/1/');
    $entry = md5($url);
    $cache->delete($entry);
    // suppose we run here the query ("update `blog` set `title` = 'test' where `path` = '$url'");
    require('class.cache.php');
    $cache = new Cache('cache/1/');
    $entry = md5($url);
    $cache->delete($entry);

The online CURL header request tool uses this cache technique to reduce the CPU loads of the web server, and this cache files will be purged every few days to prevent overloading the disk space in the web server.

Remember, page loading speed is one of the factors that affect your SEO score. Search engines favour faster websites.

–EOF (The Ultimate Computing & Technology Blog) —

GD Star Rating
loading...
716 words
Last Post: Common Javascript Functions for Financial Calculations
Next Post: Passing Variables through different pages in PHP

The Permanent URL is: How to Cache Heavy Operations (such as Database Queries) in PHP?

Leave a Reply