How to create a simple and efficient PHP cache
When working on php websites made from scratch and without a framework, speed can often be an issue. Caching is extremely useful in order to speed up PHP webpages. In this article, I’ll show you a super easy and efficient way to dynamically cache php pages that need to be faster.
Step one: Create the top-cache.php file
We need to create two files. Here’s the first one: Create a new file named top-cache.php and paste the code below in it.
<?php
$url = $_SERVER["SCRIPT_NAME"];
$break = Explode('/', $url);
$file = $break[count($break) - 1];
$cachefile = 'cached-'.substr_replace($file ,"",-4).'.html';
$cachetime = 18000;
// Serve from the cache if it is younger than $cachetime
if (file_exists($cachefile) && time() - $cachetime < filemtime($cachefile)) {
echo "<!-- Cached copy, generated ".date('H:i', filemtime($cachefile))." -->\n";
include($cachefile);
exit;
}
ob_start(); // Start the output buffer
?>
So, what this code does? The first 5 lines create the cached file name according to the current php file. So, if you’re using a file named list.php, the cached file will be named cached-list.html.
Line 6 creates a $cachetime variable which determines the life of the cache.
Lines 9 to 13 are a conditional statement which look for a file named $cachefile. If the file is found, a comment is inserted (line 10) and the $cachefile file is included. Then, the exit statement stops the execution of the script and the file is sent to the client brother. Which means that if a static file is found, no php code is interpreted by the server.
Line 14 creates a buffer, if the $cachefile file isn’t found. That’s all for the top-cache.php file.
Step two: Create the bottom-cache.php file
Now, create a second php file, named bottom-cache.php and paste the code below in it.
<?php // Cache the contents to a file $cached = fopen($cachefile, 'w'); fwrite($cached, ob_get_contents()); fclose($cached); ob_end_flush(); // Send the output to the browser ?>
If a file named $cachefile isn’t found on your server, this code is executed and create the file, so next time the page will be called, the $cachefile static file will be served to the client browser instead of executing the whole PHP file.
Step three: Include cache files on your page
Now that you have created the two necessary files, you simply have to include them on the php page you wish to cache. As you probably guessed, the top-cache.php file must be included in the beginning of your php page and the bottom-cache.php at the end, as shown below:
<?php
include('top-cache.php');
// Your regular PHP code goes here
include('bottom-cache.php');
?>
Now if you test the cache on a slow page, you’ll be amazed by how faster the page is. This easy cache is my favorite solution when working on “from scratch” PHP websites.
Nice straight forward post. Thanks.
Why use “fopen”, “fwrite” and “fclose” when you can simply file_put_contents(ob_get_contents())?
For performance gain. file_get_contents and file_put_contents are slower.
What about $_POST, $_GET, $_COOKIE, $_REQUEST values ?
list.php => cached.list.html
list?php?id=123 => cached-list.html
Same file ? o_O
Also :
/*****/
$url = $_SERVER["SCRIPT_NAME"];
$break = Explode(‘/’, $url);
$file = $break[count($break) - 1];
/*****/
is equal to this simple line :
/****/
$file = basename( $_SERVER["SCRIPT_NAME"], ‘.php’ ); // is enought
/****/
Julio, i think the point of the directory parsing is that he must most probably use like a htaccess and not have many, or any vars with _GET. (i myself hardly never use _GET vars anymore…)
You could just encode the REQUEST_URI to make a uniq identifier with _GET and all.
For the _POST, its not a real problem because i dont think you want to cache that.
Very nice script.
Thx for sharing !
I believe apache does what you are doing using Disk Cache
However If its data that is holding you down, I would use memcache. There is a php memcache client that is pretty usefull. I suppose both will require you to have access to install/configure memcache or apache. So your solution would be helpfull then.
I wrote a similar php cache about a year ago, found it to be so simple to build that I hesitated to put it into production use, thinking I must have missed something. We don’t get much traffic (~10k unique visitors / month) so I’ve kept this in my back pocket. I ended up implementing a fallback MySQL server which has been the weakest link when we get a flood of requests.
https://gist.github.com/3812913
My script handles $_GET variables and doesn’t return a cached page if $_POST is non-empty.
I found it easy and satisfying to build, and looking over it today, surprisingly readable!
If anyone looks at that gist, it’ll be the first time anyone has looked at my PHP code ever, so let me know what you think.
One big difference with my setup is I’m using the PHP setting auto_prepend which allows you to have a file automatically include()d on every PHP page, and then I use PHP shutdown functions (which are passed the full text/html that is about to be output to the browser) to save the cache file. You can specify the shutdown function in the auto_prepend code and they’ll be run at the very end. You can also set the PHP variable auto_append which works as you would expect.
Thanks for your sharing. I have a site using PHP from scratch but don’t have cache system. I think this tutorial is really what I need
Please, please use Varnish.
really useful and simple!!!
Bottom cache file??? why? Yo can define a callback before push the buffer:
ob_start(function ($buffer) {
//save $buffer
return $buffer;
});
I think we do all that effort, having already made tools is trying to reinvent the wheel.
I works in a cache package for PHP with good results in hi-performs stages:
https://github.com/exos/HybridCache
There are a class called “PageCache”, with a very simple implementation, and supports disk cache, memcache and redis
PD: Sorry my english!
Please use readfile instead of include, if i write a comment like this Your cache may redirect to mine
Simple process indeed. It is effective too.
Hey Jean-Baptiste Jung,
Another awesome post. Had no idea that cache was so easy to make. Good stuff, combined with all of the comments. This results in a solid post. Thanks.
My friend and I once made a speed test – MySQL vs raw files. I assume that if you need to get specific results from a MySQL DB, you’d be better off getting it from the MySQL DB if you needed to search for it as it is indexed rather than caching it. Right?
Else, great post. Thank you. I learned a lot from it.
This cache is a good simple solution, but isn’t as fast as it could be, as the PHP file is still being parsed. The best caching mechanism would not go through PHP at all if a cached version is available. This is what the WP Super Cache and W3 Total Cache plugins for WordPress both do.
This could be done using a rewrite rule, or via a caching proxy like Varnish or HAProxy.
I tried this one. Pretty simple and surely effective PHP cache.
I made a version now where the bottom part is called via the callback function of ob_start. So there is just one file to be included. Not too difficult, nevertheless it was quite tricky, because of a php bug, where den cad (current directory) is changed within the callback function.
One solution is to define a constant outside of the callback function with the directory in it and use that constant in the callback function
define(“CACHEFILE”, str_replace(“\\”, “/”, realpath(dirname(__FILE__))).”/cache/”.$filename);
albin
p.s.: “bottom part”
ob_start(“cache_it”); // Start the output buffer
function cache_it($buffer) {
$cached = fopen(CACHEFILE, ‘w’);
fwrite($cached, $buffer);
fclose($cached);
return $buffer;
}
Probably this function is better:
[php]
$dir_cache = dirname(__FILE__) . ‘/wp-content/cache/’;
if( !is_dir($dir_cache) ) mkdir( $dir_cache, 0777, true );
$file = $_SERVER['REQUEST_URI'];
if(strlen($_SERVER['REQUEST_URI']) > 100) {
$file = substr($_SERVER['REQUEST_URI'], 0, 100);
}
$file = str_replace(array(‘/’, ‘.html’, ‘.htm’), array(‘-’, ”, ”), $file);
$file = trim($file, ‘-.’);
$file = preg_replace(“#[\"\']#”, ”, $file);
$file = preg_replace(“#[^a-z0-9-.]#i”, ”, $file);
if(empty($file)) $file = ‘index’;
$cachefile = $dir_cache . ‘cached-’ . $file . ‘.html’;
$cachetime = 18000;
// Serve from the cache if it is younger than $cachetime
if (file_exists($cachefile) && time() – $cachetime < filemtime($cachefile)) {
echo "\n”;
include($cachefile);
exit;
}
ob_start(); // Start the output buffer
[/php]
Why? Bacouse cache is saving to the cache directory inside wp-content dir. You caching whole site but not only one file (index.php)