Fragment Caching in WordPress

Fragment caching is useful for caching HTML snippets that are expensive to generate and exist in multiple places on your site. It’s like full page HTML caching, but more granular, and it speeds up dynamic views.

I’ve been using this fragment caching class for a few years now. I optimized it around ease of implementation. I wanted, as much as possible, to be able to identify a slow HTML-outputting block of code, and just wrap this code around it without having to refactor anything about the code inside.


<?php
/*
Usage:
$frag = new CWS_Fragment_Cache( 'unique-key', 3600 ); // Second param is TTL
if ( !$frag->output() ) { // NOTE, testing for a return of false
functions_that_do_stuff_live();
these_should_echo();
// IMPORTANT
$frag->store();
// YOU CANNOT FORGET THIS. If you do, the site will break.
}
*/
class CWS_Fragment_Cache {
const GROUP = 'cws-fragments';
var $key;
var $ttl;
public function __construct( $key, $ttl ) {
$this->key = $key;
$this->ttl = $ttl;
}
public function output() {
$output = wp_cache_get( $this->key, self::GROUP );
if ( !empty( $output ) ) {
// It was in the cache
echo $output;
return true;
} else {
ob_start();
return false;
}
}
public function store() {
$output = ob_get_flush(); // Flushes the buffers
wp_cache_add( $this->key, $output, self::GROUP, $this->ttl );
}
}

view raw

gistfile1.aw

hosted with ❤ by GitHub

Implementation is pretty easy, and you can reference the comment at the start of the code for that. The only thing to consider is that any variables that alter the output need to be build into the key. It should also be noted that this code assumes you have a persistent object cache backend.

31 thoughts on “Fragment Caching in WordPress

    1. I wonder the same thing. From the codex:

      “Use the Transients API instead of these functions if you need to guarantee that your data will be cached. If persistent caching is configured, then the transients functions will use the wp_cache functions described in this document. However if persistent caching has not been enabled, then the data will instead be cached to the options table.”

      Doesn’t this basically move a step above the wp_cache* functions and make the correct decision between database and memory for us based on the presence or absence of an object caching plugin?

    2. Well, maybe. The number of things you’re storing would be one reason, and the possibility of not accessing items again would be another. DB transients are not, to my knowledge, actively garbage collected. So if you have some variable cache key portion that may eventually roll over and not be used again, you could end up with a ton of crud in your options table. All object cache backends I know of have some sort of garbage collection, even if it’s just triggered on hitting the max cache size.

    3. Wouldn’t you consider set_transient’s ‘expiration’ parameter a form of garbage collection? Even if you didn’t, it’d be pretty easy to hook into cron to collect leftovers considering you know every transient key that you’ve set. Pardon me if I’m totally off, that’s just how I would look at it.

    4. Database-stored transients are indeed not garbage collected at present. So if you use variable keys, then you can unwittingly grow your options table very large indeed.

      The solution to this is simply to not use variable keys. Not a perfect solution, I grant you, but the use of variable keys is pretty edge-casey anyway, I think.

    5. > The solution to this is simply to not use variable keys. Not a perfect solution, I grant you, but the use of variable keys is pretty edge-casey anyway, I think.

      I learnt for the first time just now that variable keys aren’t garbage-collected. I’d been using them all the time, to hand out unique, automatically expiring download URLs. My options table was growing by 40,000 entries a week! I’ve used transients as expiring access tokens on lots of sites, so this discovery is a bit worrying!

  1. This is brilliant. I’m not sure what dynamic parts of my sites would benefit from it though. I tend to “full-page” cache everything for an hour, regardless of how dynamic it might be. Of course, I’m using the APC object cache and that helps a lot with the first visitor problem.

    1. Typically things like footers and menus are good candidates. Or, say you have some “related posts” function that isn’t doing its own caching. I just inject some timers throughout and look for the slow parts.

    2. @markjaquith You wrote “I just inject some timers throughout and look for the slow parts.” Would you provide some examples of how this is accomplished? Or, if there’s a tutorial you can point to, that would be appreciated.

  2. We’ve done something very similar to this in Pods:

    http://podsframework.org/docs/pods-view/

    I call it partial page caching, where it actually does what get_template_part does, but caches it too. I find it much easier to work with than output buffering on it’s own. I’ll think about adding this kind of output() / store() functionality too, looks really quite useful for conditional PHP where it gets expensive.

  3. Thanks Mark. This is a handy little class you’ve made here. I usually write this sort of stuff from scratch each time, but I’ll try to remember to use this from now on.

    You post often, but when you do it is always guaranteed to be top notch 🙂

  4. I already tweeted this, but I just wanted to share my fork. It seems a simpler API than instantiating a class would be to use a wrapper function for a closure:


    <?php
    /*
    Usage:
    cache_fragment_output( 'unique-key', 3600, function () {
    functions_that_do_stuff_live();
    these_should_echo();
    });
    */
    function cache_fragment_output( $key, $ttl, $function ) {
    $group = 'fragment-cache';
    $output = wp_cache_get( $key, $group );
    if ( empty($output) ) {
    ob_start();
    call_user_func( $function );
    $output = ob_get_clean();
    wp_cache_add( $key, $output, $group, $ttl );
    }
    echo $output;
    }

    view raw

    gistfile1.php

    hosted with ❤ by GitHub

    1. It would require some sort of fragment caching library to be included. There’s probably something out there for this already rather than you needing to adapt Mark’s code.

  5. One of your link building strategies that you can use, to acquire good quality links from other
    sites, is definitely the link wheel strategy. It is a pay per sales affiliate program
    which is more transparent and risk free to partners. Read
    on to see if the Schwinn Joyrider Jogging Stroller
    is able to really provide what the hardcore joggers
    need in their jogging strollers.

Leave a comment