How to implement donut caching in PHPAug 6, 2011

High-traffic sites can often benefit from caching of pages, to save processing of the same data over and over again. There are various ways of implementing a cache, the easiest to implement is to use Output buffer (basically if you call ob_start() at the start of your program, it suppresses all output until you specifically flush the output buffer, therefore you can easily get the output of any PHP script, write it to a static (html) file and serve this static file to all upcoming requests for a specified amount of time).

Output buffer works very well for pages which do not change very frequently, but suppose on your website you have to show logged in user's name (somewhere in website header), everything else will be same for all users. In this situation you cannot use Output buffer because you have to check dynamically weather a user is logged in or not. So how do we use cache in this situation? Answer is "Donut caching".

Donut caching means that although you are caching your content but you have some holes in it so that you can still affect the output that goes to the user. By using donut caching you can cache pages on your site and still show different welcome message for all users (i.e. Name for logged in users or some other message if they are not).

In this post I will explain you how we can use donut caching in PHP, so let's start:

<?php
    session_start();
    $filename = $_SERVER["SCRIPT_NAME"];
    $cachefile = "cache/" . str_replace('/', '_', $filename) . ".html";
    
    if (file_exists($cachefile)) {
        $is_orignal_file_modified = filemtime($_SERVER["DOCUMENT_ROOT"] . $filename) > filemtime($cachefile);
        if (! $is_orignal_file_modified) {
            include($cachefile);
            echo "<!--Cache HIT-->";
            exit;
        }
    }

    // start the output buffer
    ob_start();
    $data = array("title" => "page title");
    include_donutfile("header.php", $data);
?>

... Your usual PHP script (DB queries, data processing etc) and HTML here ...

<?php
    file_put_contents($cachefile, ob_get_contents());

    // clear output buffer
    ob_end_clean();

    // Include cache file so that if there is any donut it will be filled now
    include($cachefile);

    echo "<!--Cache MISSED-->";

?>

Add the following code in header.php (where you want to create donut) :

<!--DONUT_START-->
<?php
    if (isset($_SESSION["name"])) echo $_SESSION["name"];
    else echo "guest";
?>
<!--DONUT_END-->

Note: code written between <!--DONUT_START--> and <!--DONUT_END--> will be used as donut

Now let's create the magic function "include_donutfile"

function include_donutfile($file, $data=array()) {
    if (! file_exists($file)) return;

    $tmpfile = 'cache/tmp_' . md5($file) . '.php';
    if (! file_exists($tmpfile)) {
        $content = file_get_contents($file);
        $content = str_replace('<!--DONUT_END-->', 'DONUT;
echo $donut;?>', str_replace('<!--DONUT_START-->', '<?php $donut = <<<\'DONUT\'', $content));
        
        // Code not executed yet write it to a tmp file and include
        file_put_contents($tmpfile, $content);
    }

    extract($data);
    include($tmpfile);
}
- This will work only with PHP 5.3 or above (as nowdoc is used).
- <!--DONUT_END--> must be at the begining of the new line

Thats all, enjoy caching even for logged in users :)



blog comments powered by Disqus
Me Hi! My name is Zeeshan Muhammad Khan (nick name Shan) and I am a software engineer, database developer, web developer, programming geek, statistics geek, mathematics geek, system analyst and maintainer of this site. read more

Web Shelf