HTML Cache: CodeIgniter3 + PhpFastCache + Redis

Website performance has been the key factor for user engagement. Since more and more users are using tablets and mobiles for internet access, it is very important to have a fast loading website in place. Though there are multiple ways to enhance your site, I would like to concentrate on caching. I have implemented caching using the following:

CodeIgniter: I have been working with CodeIgniter for almost 8+ years. Since then, the framework has been my favorite. We will be using CodeIgniter3 for this tutorial. Though it should be easy to work with any framework.

PhpFastCache is a good caching library that supports multiple database drivers. You can read more here. I could have used the default cache feature in CodeIgniter. But PhpFastCache provides better handling when it comes to cache management. Installing the library is pretty straight forward. Hoping you are familiar with composer, use the following command

composer require phpfastcache/phpfastcache

Redis: Redis is a in-memory database. It is really fast for saving and accessing data because all of the data is saved in memory & not the disk. Installing it over Ubuntu us pretty simple. Use the following command

sudo apt-get install redis-server

Once installed, you can manage your Redis server with following commands:

sudo service redis-server start/stop/restart

To work on Redis with PHP you need to install the php-redis extension. You can do this as follows:

sudo apt-get install php-redis

sudo service apache2 restart // restart the apache server

Once you are done with all this, you are ready to dive in the code now.

Library: Cachelip.php

First you need to create a library class file for the cache library. The library would have a few functions and a constructor. Let us name this file Cachelib.php. The file will have the following code

<?php if (!defined('BASEPATH'))
{
    exit('No direct script access allowed');
}
/* the use statements from PhpFastCache */
use Phpfastcache\CacheManager;
use Phpfastcache\Drivers\Redis\Config;

Some private variables and a constructor to load & connect to the Redis instance

/**
 * Cache library
 */
class Cachelib
{
    private $CI;
    private static $instance = null;
    private $conn;

    public function __construct()
    {
        $this->CI = &get_instance();

        try {
            
                $this->conn = CacheManager::getInstance(
                    'redis',
                    new Config(
                        array(
                            "host" => "127.0.0.1",
							"port" => 6379,
							"database" => 0 // redis databases are identified by index
                        )
                    )
                );
            
        }
        catch (Exception $e)
        {
            $message = $e->getMessage();
            $messageArray = explode("line", $message);

           // Log the error message if required

            show_error($message, 500);
        }
    }

A function to get the instance for the connection manager

   /**
     * function to get connection instance
     *
     * @return object
     */
    public function getConnection()
    {
        return $this->conn;
    }

Next function is important to understand. Redis provides key-value pair to save the data. The PhpFastCache library extends this functionality by providing extra features like adding tags to the the key-value pair. A key-value pair is good to have. But the problem starts when you have to delete the bulk data. Let us take an example of an author profile. Let us say that the authors have a unique profile_id but they also have some attributes like language. You can delete the the cache value with the key = profile_id. The problem usually starts when you have to delete the cache for all authors by language. Since Reid only allows key-value pairs, the PhpFastCache allows you to put tags to the values as well.

ID              Name        Book          Language
01               Harry        Book1           English
02               Zarah        Book2           Arabic
03               Michael      Book5           English
04               Terry        Book3           Punjabi
05               Tony         Book6           Arabic

We can delete the above cache with ID as a key, but with this library it is also possible to delete all cache values with language english only. All we need to do is set tag(s) while saving the data. The function to save such data would look like this.

/**
	 * function to set cache
	 *
	 * @param string $key the key for the value
	 * @param mixed $data the data needs to be saved
	 * @param array $tag the tag for data
	 * @param integer $expire in seconds
	 * @return void
	 */
    public function setCache($key, $data, $tag=array(), $expire = 0)
    {
		if (!$texpire) {
			$expire = $this->CI->config->item("cacheExpireTime"); // from the config file
		}

		if (!empty($tag) && is_string($tag)) {
			$tag = array($tag);
		}

        $CachedString = $this->conn->getItem($key);
        $CachedString->set($data)->expiresAfter($expire)->setTags($tag);
        $this->conn->save($CachedString);
    }

This piece of code would save your data with a key and multiple tags associated to it. For an example, I can save some information as follows:

//Save Harry's record with tags "harry" & "english"
$this->cachelib->setCache(
    "01",
    "Harry wrote book1 ",
    array(
        "harry",
        "english"
    )
);

//Save Michael's record with tags "michael" & "english"
$this->cachelib->setCache(
    "01",
    "Harry wrote book5 ",
    array(
        "michael",
        "english"
    )
);

With the code above, Redis will save two records with ID and other records with TAGS pointing to the original records. The PhpFastCache library handles Redis pretty well with with. With this setup user can access & delete data with tags as well. Below are the other required functions.

     /**
     * function to get cache data
     *
     * @param string $key
     * @return mixed
     */
    public function get($key)
    {
        return $this->conn->getItem($key)->get();
    }

     /**
     * function to clear cache for
     * @param  string $for
     * @param  string $by 
     * @return boolean
     */
    public function clear($for = "all", $by="tag")
    {
        if ($for == "all")
        {
            return $this->conn->clear();
        }
        else
        {
            if ($by == "tag") {
                return $this->conn->deleteItemsByTag($for);
            }
            elseif($by == "key"){
                return $this->conn->deleteItem($for);
            }
            else{
                return $this->conn->deleteItem($for);
            }
        }
    }
        /**
	 * function to check if key is already cached
	 *
	 * @param string $key
	 * @return boolean
	 */
    public function isCached($key)
    {
        $CachedString = $this->conn->getItem($key);
        return $CachedString->isHit();
    }

Controller: Welcome.php

Let us move on to the controller and how should we be using our library functions to manage the data with Redis. Since now you have the weapon, it depends on you to use it. Let us dive into more details to get this done.
The welcome controller should look like this:

<?php defined('BASEPATH') or exit('No direct script access allowed');

class Welcome extends CI_Controller
{
    /**
     * index function
     * @author    Harpreet Bhatia
     * @return html
     */
    public function index()
    {
        /* key name */
		$key = "home_page_key"

		/* get ready with tags */
		$tags = array(
			$key,
			"some_tag",
			"module_name"
		);

		/* check if key has been cached */
        if ($this->cachelib->isCached($key))
        {
			/* get the cache value */
			$html = $this->cachelib->get($key);
			$this->load->view(
				"html", array(
					"html" => $html
				)
			);
        }
        else /* if the key was not previously cached */
        {
			/* detch all the data here */
			/* $data["content"] = $this->model->some_function() */

			$data["template"] = "home/index";

			$html = $this->load->view("template", $data, true);

			/* save data to cache */
			$this->cachelib->setCache($key, $html);
			/* WITH TAGS =====> $this->cachelib->setCache($key, $html, $tags); */
			
			/* load the view */
			$this->load->view(
				"html", array(
					"html" => $html
				)
			);
			
        }
    }
}

As you can see above the code checks for existing cache. If found, gets the value(the processed HTML) and load it. If not found, we fetch all required data and pass it to HTML. If you see it clearly we are caching the whole html instead of the $data. Caching & displaying HTML is better and faster than caching just the $data.
You can use the library functions to delete the cache with key or tags.
You can delete home page cache with the key.

$this->cachelib->clear("home_page_key", "key");

Or you can delete multiple cache with tags:

$this->cachelib->clear("module_name", "tag");

I hope it helps you guys. I am open for opinions & suggestions.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.