Ultimate Wordpress Caching (not for the non-programmers)

I recently started on a new site, php-development-guide.com, and I decided to use wordpress (though i altered it to act as an article cms)… and as usual found that my shared host has terrible database performance.
Well, I found the best way to curb this issue is to actually not do any database calls whatsoever. The issue with plugins like w3 total cache and others is that they exists at the plugin level, so database connections and calls are made even with their caching attempts. However, if you put your caching code at the beginning of all code execution, you can get around that.
For wordpress, all pages are loaded via the main index.php (NOT the template file!). The main index.php is contained at the root of your wordpress install. For me, the index.php file only contained these lines:

[php]
// comments

define(‘WP_USE_THEMES’, true);

/** Loads the WordPress Environment and Template */
require(‘./wp-blog-header.php’);
[/php]

Here is the caching code I came up with:

[php]

<?php // comments from wordpress... // since this is before any wordpress debugging // if you want error reporting for tests, uncomment this line //error_reporting(E_ALL); // Grab page url to make a cache file name $getPageName=str_replace('/','_',$_SERVER['REQUEST_URI']); // use whitelist regex for extra security, otherwise you can do things like directory traversal (aka, .../) $getPageName=str_replace('_.','.',preg_replace('/[^ 0-9A-Za-z\_]/', '',$getPageName).'.txt'); // If requesting home page there will be no file name, so set one if($getPageName=='.txt') { $getPageName='homepage.txt'; } // Directory where you cache files will be stored. Directory should have write permissions for everyone $cacheDirectory='cache/'; // Set expire time, text is based on what strtotime() php function supports $expireTime='5 days'; $cacheFileExists = FALSE; // Check if cache file already exists // if so, get last modified date and compare it to current date (to see if it's expired) if(file_exists($cacheDirectory.$getPageName)) { $cacheFileExists = TRUE; $cacheFileModifyDate = date ("r", filemtime($cacheDirectory.$getPageName)); $currentCacheDate = strtotime(date("r")); $cacheExpirationDate =strtotime($cacheFileModifyDate."+".$expireTime); } else { // otherwise, set all info to '' $cacheFileModifyDate = ''; $currentCacheDate = ''; $currentCacheDate = ''; $cacheExpirationDate =''; } // If cache file expired, or if cache file doesn't exist, generate a new one if($currentCacheDate >= $cacheExpirationDate || $cacheFileExists == FALSE) { // Use output buffer to grab contents generated by wordpress ob_start(); define('WP_USE_THEMES', true); /** Loads the WordPress Environment and Template */ require('./wp-blog-header.php'); // Grab output buffer contents $cacheFileContents=ob_get_contents(); // Flush to browser before recording cached contents ob_end_flush(); // Open file for writing $cacheFileOpen = fopen($cacheDirectory.$getPageName, 'w'); // Write cache contents to file and close file fwrite($cacheFileOpen, $cacheFileContents); fclose($cacheFileOpen); }else{ // If file is cached and not expired, grab and echo contents echo file_get_contents($cacheDirectory.$getPageName); } ?>

[/php]

This drastically cut down on the time to first byte and brought me from an F to an A. When the cache is on, the only thing php has to do is check if the cache file exists and is not expired, if it passes, it loads and outputs text straight from the cache file to screen. No database calls or anything is done and so backend processing is almost non-existant.

The Results
No Cache, TTFB= 3.074 seconds:
http://www.webpagetest.org/result/120131_4D_32BRA/1/details/

With Cache, TTFB= 0.414 seconds:
http://www.webpagetest.org/result/120131_A7_32BRK/1/details/

Some things to note:
First I designed this for my site, where I turned wordpress isn’t an article cms, and did so with no user comments. So for me, as it is now, it’s fine. However, if you have users placing comments via wordpresses internal comment system (as opposed to something like disqus), you will need to program something to invalidate the cache (like delete the cache file) if a new comment is made. This should be easy enough, you just have to edit the wp-comments-post.php file, grab and generate the current url and then delete the cache file. Or, the other option is to deal with the comments not showing by just setting a lower cache time (like 3 hours). But it may seem odd to the user if the comments don’t show right away, so i’d still recommend invalidating the cache.
Secondly, since this is editing the core wordpress files (as it has to), it will get overridden when a wordpress update is done. But these additions are so generic you can easily re-apply them with each update.

Would a person need to be logged in to post a comment? If yes, you could (if it would be easier to implement) serve cached pages only to non-logged in guests, and serve fresh content only to those who are logged in.

Well, I believe most wordpress installs allow you post a comment without logging in, which is why you’d need the cache invalidation code within the wordpress comment submission code. Though, if you rely on an external comment system, loaded via ajax like disqus, then that is a non-issue because the cache will not include the comments.
In general though, I think someone could set this type of system up and make use of it and w3 total cache. That way, you can set a lower cache time, and when the cache has expired, the initial load time is less still than without the w3 plugin.
Eitherway, the system as-is is good for me. If I do add a comment system, it will probably be something like disqus, and with the system my site is quite zippy… aside from the fact that the host has keep-alive’s disabled, but that’s another issue.

yes, the live functions get defeated easily with caches.
I worked on this for a while, and used two systems to decide to feed out cached pages.
I use a php based caching, simlar to OP.

  1. all comments are moderated, no need to login to post. so the “live” response is not so important. (mine is an ecommerce wordpress site, interaction goes TO my office mostly, to do biz)
  2. I serve cached pages to non-logged in users, based on cookie. If more than 3 pages viewed I serve live. (they have many objects already in cache… usually) )note: i have since bypassed this step, but it’s useful for some.
  3. I use a cron job to delete files over 1 hour old rather than check live if cachefile is expired (faster). )this subbed in for the cookie check.
  4. to solve issues with comments post, it’s easy to have the comments.php page set the visitor cookie to a value of 10, for example, so that on post, page refreshes live, and they continue to view live forwards. – or you can live with a one hour lag on comments going live… depends on the job.

For #3, do you still check if the cache file exists or do you just serve it and assume it exists? Reason I ask, is if you do check if the file exists first, then technically you could just filemtime() and on getting false assume the file does not exists, otherwise it does. That would make it so you don’t need a cron but still have the same overhead as before. If you do not check if it exists first, how do you handle it if it doesn’t? Just curious, this is my first pass at my caching function, looking for ways to make it more efficient.[hr]
By the way, I’ll probably post another version of the cache function soon. Learned recently that you can pass a callback function to ob_start. That will allow me to put all logic into a neat little function and just call ob_start(‘functionName’). Should make it easier to implement, as it could then be implemented as a simple include in the main index.php file.

I use this: it checks if file exists, if yes, feeds it out, if not, it uses standard wordpress to feed out the html file to visitor… then proceeds to save it AFTER visitor has received the page. the CRON simply subs in for php calls to check time (it deletes files older than 2 hours). so I set it to 2 hours and does in background. You could easily check if cache file exists, and if expired… but I kept it simple with Cron for speed.

======header section ====

php
global $cachefile;
$cachefile = ‘’ ;
if ( !is_user_logged_in()) {

// Settings
$cachedir = '/cache/' ; // Directory to cache files in (keep outside web root)
$cacheext = 'htm'; // Extension to give cached files (usually cache, htm, txt) 

// Script
$page = 'http://myserver.com' . $_SERVER['REQUEST_URI']; // Requested page
$cachefile = $cachedir . md5($page) . '.' . $cacheext; // Cache file to either load or create

// Show file from cache 

if (file_exists($cachefile)) { @readfile($cachefile); exit(); }

// If we're still here, we need to generate a cache file
ob_start();

}

your html stuff here, including standard php calls

==== then FOOTER section // BOTTOM of your script after </html tag
php
global $cachefile;
if (!empty($cachefile)) {
$fp = fopen($cachefile, ‘w’); // open the cache file for writing
fwrite($fp, ob_get_contents()); // save the contents of output buffer to the file
fclose($fp); // close the file
$cachefile=“”;
}

I’ve come to using this after gleaning from various sources, for example at

and here
http://www.addedbytes.com/for-beginners/output-caching-for-beginners/

overall it works very nicely for my site here
http://greenspotantiques.com

(serving wordpress site from office iMac, off DSL with upload speed of 616 kbps, images offloaded to web-hosting)