skip to content

PHP: Using Output Buffering to Cache PHP Files

First of all, credit to Dennis Pallett for his code presented on PHPit (see link under References below) which helped to demystify output buffering. The code presented here is adapted from his script, with a number of changes to make it more suitable especially for Linux/UNIX systems.

Creating Cache Files using Output Buffering

The principal behind output buffering is that you generate a page as normal using PHP, but the output is stored in memory rather than being sent directly to the browser. This gives a chance for lazy programmers to send some new header commands, but also makes it possible for us to write that output to a file.

The logic behind the following script, which you can simply include at the top of a page, is as follows:

  1. IF a cached version of the page exists AND is not too old THEN display that file and exit: cache_display();
  2. ELSE, write the output to a file AND display it to the browser: cache_page().
<?PHP // Adapted for The Art of Web: www.the-art-of-web.com // Based on PHP code by Dennis Pallett: www.phpit.net // Please acknowledge use of this code by including this header. // location and prefix for cache files define('CACHE_PATH', "/tmp/cache_"); // how long to keep the cache files (hours) define('CACHE_TIME', 12); // return location and name for cache file function cache_file() { return CACHE_PATH . md5($_SERVER['REQUEST_URI']); } // display cached file if present and not expired function cache_display() { $file = cache_file(); // check that cache file exists and is not too old if(!file_exists($file)) return; if(filemtime($file) < time() - CACHE_TIME * 3600) return; // if so, display cache file and stop processing readfile($file); exit; } // write to cache file function cache_page($content) { if(false !== ($f = @fopen(cache_file(), 'w'))) { fwrite($f, $content); fclose($f); } return $content; } // execution stops here if valid cache file found cache_display(); // enable output buffering and create cache file ob_start('cache_page'); ?>

Note: save this script as buffer.php and include it at the top of your HTML/PHP files.

This script assumes the existence of a /tmp/ directory which is writable by the webserver. If this isn't the case, or you want to store cache files in a different location, you need to create a directory that is writable by the webserver (chgrp to the webserver user and chmod 775) and change the CACHE_PATH constant accordingly.

It also assumes that $_SERVER['REQUEST_URI'] is defined, which isn't always the case on Windows XP or Microsoft IIS. See the user comments with the PHP documentation on Predefined Variables for a work-around.

How do I use this script?

You need to include this script before you output any content to the page. The subsequent HTML and PHP code will then only be accessed as necessary to update the cache file (once to start with and then every CACHE_TIME hours).

Some things that you might want to consider if you use this on a live site:

  • Make sure to exclude sections of the site that need to be dynamic, forums for example; and
  • Make sure that any Content Management System you use that updates site content also knows to delete the relevant cache files, otherwise you'll be waiting hours to see the website update.

Other then that it really is trivial. The advantage of using /tmp/ as the destination directory is that services such as tmpreaper can automatically clean up any garbage left behind when your website structure changes or pages are removed.

Otherwise to remove a cache file using PHP you'll need the following function:

<?PHP function cache_remove($path) { $file = CACHE_PATH . md5($path); if(file_exists($file)) unlink($file) or die("Could not remove file: $file!"); } ?>

where $path is the REQUEST_URI of the page to be 'un-cached' and CACHE_PATH is as defined earlier.

Advantages of Output Buffering over other methods

The beauty of this solution is that it's completely independent of the method you use to generate content. Whether pages are coded by hand, generated by a CMS or pulled from an external source makes no difference. Even the addressing scheme is irrelevant. Another benefit is that the cache files can be removed whenever you want without effecting the website. Finally, the whole script weighs in at just 20 lines of code!

Contrast this to other methods where you have a horribly complicated CMS generating PHP files that are then parsed and served to the browser. If this is the only way of generating new content, or editing existing content, then it becomes difficult to make even some simple changes.

An alternative approach is to 'spider' a dynamic site, one with search engine friendly URL's, to create a static version that can then be served from the same or another webserver. Again, as a webmaster you're a step removed when it comes to updating the content.

Before anyone jumps in with their favourite caching system, I'm sure it's very good but there are a lot of them - all with different advantages, but nothing this simple.

Zipping the Cache

Following a query from McClain (see Feedback below) we can also show you how to store the cached content in a zipped format. This means we zip the content as it's written to the cache directory, and unzip it for display.

Many servers and browsers now support delivery of all HTML/text content in a gzipped format, but that's not what we're talking about here. In this case the data would be stored zipped, but delivered as normal HTML/text. Delivering zipped content requires additonal work.

The changes have been highlighted:

<?PHP // Adapted for The Art of Web: www.the-art-of-web.com // Based on PHP code by Dennis Pallett: www.phpit.net // Please acknowledge use of this code by including this header. // location and prefix for cache files define('CACHE_PATH', "/tmp/cache_"); // how long to keep the cache files (hours) define('CACHE_TIME', 12); // return location and name for cache file function cache_file() { return CACHE_PATH . md5($_SERVER['REQUEST_URI']); } // display cached file if present and not expired function cache_display() { $file = cache_file(); // check that cache file exists and is not too old if(!file_exists($file)) return; if(filemtime($file) < time() - CACHE_TIME * 3600) return; // if so, display cache file and stop processing echo gzuncompress(file_get_contents($file)); exit; } // write to cache file function cache_page($content) { if(false !== ($f = @fopen(cache_file(), 'w'))) { fwrite($f, gzcompress($content)); fclose($f); } return $content; } // execution stops here if valid cache file found cache_display(); // enable output buffering and create cache file ob_start('cache_page'); ?>

This will significantly reduce the size of the cache directory, but also add some load to the server for zipping and unzipping content.

Again, this is not the same as gzip compression, which includes some header data.

References

< PHP

User Comments

Post your comment or question

7 January, 2014

for the cache folder CHMOD 700 is working great for me, but I made some changes to make the script do the job at my topsites and link trade sites..

@krivchev: on the Adult SEO Toplist (Based on Aardvark Topsites) I use this script to cache "custom pages" and yes my menu updates when I make changes to the menu and counting votes etc is working as well..

To make this work you should add this cache script BELOW the items you do NOT want to cache so the cache script will execute later, it's working for my sites so it should work on other sites as well...

Happy coding!

27 July, 2013

Hi, can I add something like this: if(filemtime($_SERVER['REQUEST_URI']) > filemtime($file)) return;
after:
if(!file_exists($file)) return;
to see if the file has been edited or the requested time wastes the benefit?

To compare the timestamp of the current page to that of the cache file:

$self = $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']
if(filemtime($self) > filemtime($file)) return;

But remember this only looks at the modification time of the HTML/PHP page and not any includes or (database) generated content.

12 June, 2013

Thank for helpful article. But what about if we update some part , like categories or menu items ? Are they visible for user immediately ?

The content will be cached, preventing any changes being displayed, for up to CACHE_TIME hours. The cache_remove function is for if you want to flush the cached version earlier.

6 November, 2011

works really nice!
I found something similar on github:
It's really similar but implements a few more features: you can add a blacklist using Regular Expressions or implement your own function to generate the storage key. With this you can. for example. display different content for different web browsers! It can be found at github.com/marcelklehr/zoocache

17 January, 2011

Thanks for sharing the cache script.

I was wondering how I might gzcompress the $contents sent to the file and then gzuncompress the $contents to send to the browser.

This would reduce the size of your cache directory (tmp) but also send the proper response to the browser.

To compress the contents you should be able to just change the write command to:

fwrite($f, gzcompress($content));

and the readfile/output command to:

echo gzuncompress(file_get_contents($file));

I'll do some testing

top