Website Speed Part 3 – Caching WordPress

As so many people have it installed, I thought I would share with you a key techniques integral to caching WordPress that will help your website speed.

W3 Total Cache is a dream plugin for achieving caching, I would recommend it to anybody. Yet there are times when you do not want to cache a whole page for long periods, but to cache sections of it so that a page rebuild is not as costly in server time.

For example I had a site where they did not want caching on the homepage as a whole as they wanted to have the following features :

  • • Rotating background image provided by php
  • • Alternating advertising scripting for comparison of suppliers
  • • A loop that updates every 5 minutes, along with others that updated hourly

It was chosen that a “sub caching” (to use the customers preferred term) strategy would be best. Now you understand the scene of what we need to achieve, a cached homepage which could have items within it updated regularly, that did not use the page cache of W3TC.

To achieve all of what the client wanted, I needed to use the Transients API for WordPress

WordPress Transients API

The WordPress transients API is a simple set of three commands that work very much like Options API, but when a timer that you set runs out, the transient is automatically deleted. Inside a transient you can save any kind of data you like. It can be an array, object or just some text, it really does not matter what you store in there as WordPress takes care of it by serializing it as necessary.

What the Transients API also does is automatically save the transient in the quickest responding place on the server. This could be in the database, on the disk or in memcache depending on your servers configuration.

Here is the basic syntax for getting and setting a transient:

<?php
$value = get_transient( 'value' );
if ( false === $value ) {
        // if transient not set, do this!

        // create the data that needs to be saved.
        $value = 1;

        // save the newly created transient value
        // 60 seconds * 60 minutes * 24 hours = 1 day
        set_transient('value', $value, 60*60*24);
}
?>

Notice how they work in tandem with each other. If there is nothing set in the transient called ‘value’ then the code knows to set a new ‘value’ from the code inside the if statement.

As you can see, all we do to set a transient is use set_transient() with the name of the transient, in this case ‘value’, with it’s $value being the data you want to save, and the last part being the calculation that would created the number of seconds that the transient should last for.

Using transients on a single loop inside WordPress

Unless you are an ultra busy site with 100’s of content creators, you can be pretty sure that the loop that shows your posts in your blog does not change that often. Be that days or only minutes, caching will speed up the loop, as the server will not have to ask any complex SQL queries, but a simple single request to get the pre-found data, a much quicker and less intensive thing to do.

<?php
$loop = get_transient( 'loop' );
if ( false === $loop ) {

       // Show the last 100 tweets from the custom post type tweets.
       $query = array('post_per_page' => 100,
                     'post_type' => 'tweets',
                     'post_status' => 'publish' ) ;

       $loop = new WP_Query($query);

        // transient set to last for 1 hour
        set_transient('loop', $loop, 60*60);
}
// do normal loop stuff
if ($loop->have_posts()) : while ($loop->have_posts()) : $loop->the_post();

// show content or whatever you like
the_content();

endwhile;endif;

?>

The above query for the 100 tweets from the custom post type of tweets will now be saved inside the transient called ‘loop’

A Question of Data or Information

Using the above transient we are dealing with Data, or un-formatted information. This is all fine, and works just great, but you could take things a little further.

Instead of manipulating the data every time that the page is loaded, it can be better to add all of the HTML to the loop and then save it to the transient so that it is even quicker to load.

As shown in the below widget, all of the widget is built as a full HTML section before it is saved. This is my preferred method of implementation as there is less processing to do.

Caching Widgets with transients

Many widgets show content that hardly ever changes, for example categories on the site, or pages on the site. It’s only really things like latest comments that tend to change often, and even then it’s doubtful that would be more than once per minute. So even with the more active widgets there is space for caching, let alone the sedentary ones.

If you have a busy site for some reason, that could be thousands of hits inside that minute that take up unnecessary processing time to check again and again all of the widgets and their output.

Caching widgets is pretty easy, here is an example widget for displaying a table of events from a custom post type called ‘ak_events’.

class show_ak_events_Widget  extends WP_Widget {
    function show_ak_events_Widget() {
            /* Widget settings. */
            $widget_ops = array( 'classname' => 'ak-events', 'description' => 'Shows events in a table' );
            /* Widget control settings. */
            $control_ops = array( 'width' => 300, 'height' => 350, 'id_base' => 'ak-events' );
            /* Create the widget. */
            $this->WP_Widget( 'ak-events', 'Show Events', $widget_ops, $control_ops );
    }

    function widget( $args, $instance ) {
            extract( $args );
            // get cache if it exists
            //  $widget_id comes from the widget $args->widget_id and is the widgets unique ID
            $output = get_transient('events'.$widget_id);

            // if no $output do stuff inside this if statement
            if ( $output === false ) {
                 // set the title variable
                 $title = apply_filters('widget_title', $instance['title'] );

                // standard opening of widget
                $output = $before_widget;

                // if a title exists add it to the top of the widget
                $output .= ( !empty( $title ) )? $before_title . $title . $after_title : "" ;

                // Create query arguments for WP_Query to use
                $widgetargs =  array( 'posts_per_page'=>'-1',
                                       'post_type'=>'ak_events',
                                       'post_status'=>'publish'
                                       );

                // WP_Query sets up a loop query
                $query = new WP_Query( $widgetargs );


                // create the opening table and top row
                $output .= "<table><tr><th>Event Name</th><th>Information</th></tr>";

                // If the WP_Query has results send them through the loop
                if ($query->have_posts()) : while ($query->have_posts()) : $query->the_post();
                    $output .= "<tr><td>" . get_the_title() . "</td><td> " . get_the_excerpt() . " </td></tr>";
                endwhile;endif;

                // close the table
                $output .= "</table>";

                // close widget properly
                $output .= $after_widget;

                // save $output as a transient and set it to be 60 seconds * 5 = 5 minutes.
                //
                set_transient( 'events'.$widget_id, $output, 60*5 );
            }

            echo $output;

    }

    function update( $new_instance, $old_instance ) {
            // save form data
            $instance = $old_instance;
            $instance['title'] = $new_instance['title'];

            // delete the transient so the new title setting is used
            delete_transient('events'.$this->id);
    return $instance;
    }

    function form( $instance ) {
         $defaults = array(
           'title'=>''
       );
        $instance = wp_parse_args( (array) $instance, $defaults ); ?>
         <p>
            <label for="<?php echo $this->get_field_id( 'title' ); ?>">
                <?php _e('Title:','proving-ground'); ?>
            </label>
            <input id="<?php echo $this->get_field_id( 'title' ); ?>"
                      name="<?php echo $this->get_field_name( 'title' ); ?>"
                      value="<?php echo $instance['title']; ?>" style="width:95%" />
        </p>


        <?php
     }
}

As you can see, all of the loop and processing activity takes place inside the if statement that runs when the widget is called. The other thing to notice is that there is also delete_transient() used inside the update function to delete the transient every time you update the widget on the admin screen.

The bit that makes this work is really the ‘events’.$widget_id bit, as that assigns a unique transient name to each instance of the widget that is loaded. So the widget used in one sidebar is cached separately from the same instance of the same widget .

Using caching for theme options

Theme options are more and more common inside WordPress themes. They hold things like the settings for the theme which are applied every time the site it loaded. This is fine, but it is requested from the database each time, as that is the way that get_option() works.

If you use get_transient() instead you might not be going to the database to request it. It’s possible it’s coming from memory cache or disk cache, which are quicker to access.

Here is a simple little function I made to manage this for me, whenever I need to get_option() I use my_get_cache_option() to get the WordPress option I want. The following code goes in your functions.php

<?php
function my_get_cache_option($option_name = 'ThemeAdminOptions' ){
    // get wanted transient
    $value = get_transient( $option_name  );
    // check if it has any content
    if(false === $value){
        // if no content in the transient get new copy of wanted option
        $value = get_option( $option_name );
        // set new transient with a refresh of 1 day
        set_transient( $option_name, $value, 60*60*24 );
    }
    // return the transient $value or newly created $value
    return $value;
}
?>

Personally I setup default of ‘ThemeAdminOptions’ and use that for my theme options, but if I want to get a different transient or option I use it like this:

<?php
// get cached option
$options =  my_get_cache_option('WantedOptionName');
// do code with options array/object/string
// example code
echo $options['google_analytics_id'];
?>

If you are going to use this solution, don’t forget to use delete_transient() within the process of saving the updates on your admin pages. Normally you would just update the options, but as well as that you need to delete the transient that exists as your other code will pull the old data from the transient for up to 24 hours.

<?php
// update the option as normal
update_option('WantedOptionName', $value );

// delete the transient that may be set
delete_transient('WantedOptionName');

?>

Using transients for timed events

One of the great benefits of the Transients API is the ability to setup very simple timed events that perform functions for you.

Say you wanted to get an RSS feed every minute from a stock trading company, it could be easily achieved by using the following function and call:

<?php

function setup_timed_event(){

    // get value if set;
    $value = get_transient('run_batch');

    // if not set run if statement;
    if($value === false){
            // run every time the timer runs out
            do_minute_batch();


            // set any value to $value;
            $value = 'done';

            // setup transient to last for 60 seconds;
            set_transient('run_batch', $value, 60);
    }
}



function do_minute_batch(){
 //
 // code to run in batch here that calls the RSS feed.
 //
}

//
// use an action to call the timed event every time the init is called. 
add_action('init','setup_timed_event');
?>

It is not strictly true that it will run every 1 minute, as if the server performs no activity of any description for more than a minute, it will not run, but will run on the next activity whatever that is.

Other activities that can be cached with transients

The list is endless of things that you can make quicker on a website by using the transients API. Anywhere you have to process data, you can use it. If you write a twitter plugin, you could use it to save the Twitter feed and only update when it is deleted. Or maybe you have an Amazon store that you want to setup with caching for small periods, this is perfect and simple to use. In fact anywhere you need to get data from an external source, cache it with the transients API.

There is nothing to stop you taking caching to the nth degree with multi loops with differing cache timers based on importance of content, or make sure that every single widget you have caches for the right content lifetime. In fact there is nothing to stop you giving the caching timeout control to the widget administrator by having a field that is the number of seconds or the calculation that sets the time period of the widgets set_transient(). It really is endless where you can go with this.

What about things that echo to the screen?

In WordPress there are quite a few things that automatically echo to the screen, such as the_content(). Sometimes you want to use these items rather than the get equivalent get command, i.e. get_the_content(), as the formatting is already good without the need for extra manipulation.

Or maybe a plugin developer has added an action or filter to the command, for example the_content() could also have an action or filter added to it that appends share links and icons to the end of the content.

Both of these examples call for a slightly different way of saving the data in to the transient. We now have to use an ob_start(),ob_get_contents(), ob_end_clean() combination to save the data we are echoing to the screen and then save that as a variable as the transient

<?php
$loop_output = get_transient( 'loopOutput' );
if ( false === $loop_output ) {

       // Show the last 100 published posts.
       $query = array('post_per_page' => 100,
                      'post_status' => 'publish' ) ;

       // run the query
       $loop = new WP_Query($query);

       // start the output buffer to save contents of loop
       ob_start();

            // do normal loop stuff
            if ($loop->have_posts()) : while ($loop->have_posts()) : $loop->the_post();

                // show content or whatever you like
                ?><h1><?php the_title() ?></h1><?php
                the_content();

            endwhile;endif;

            // save the output buffer contents in a variable
            $loop_output = ob_get_contents();

       // clean the buffer as we will be using the variable from now on
       ob_end_clean();

       // transient set to last for 1 hour
       set_transient('loopOutput', $loop_output, 60*60);
}

// output the new created loop if loop content does not exist. 
echo $loop_output;

?>

Rounding up

Using the transients API can significantly increase the speed of your WordPress plugin, theme or widget. It really is worth considering making this standard practice when creating for WordPress in future.

Update

Thanks loads to Erik for pointing out that you have to have WP_CACHE enabled for any of this to work. As the ChiefAlchemist rightly says, that would cause many a grey hair and long night not to know.

To set WP_CACHE, you need to edit the wp-config.php in the root of your webserver (or one directory above if you are security minded).

define('WP_CACHE', true);
/* Absolute path to the WordPress directory. */

It goes at the end just above the line “Absolute path to the WordPress directory”

Caching Shortcodes

Also by request, shortcodes. The only issue with caching shortcodes is that you need to consider where and how it will appear. here are a few different examples of where you might find yourself using shortcodes.

  • Shortcode without any settings, but relates to the post. i.e. share links to social networks
  • Shortcode with settings. i.e. show a gallery by ID
  • Shortcode with no settings or relationships. i.e. show current number of facebook followers
  • A mix of all of the above
// shortcode without any settings but relates to the post.  can only be used inside the loop.
// shortcode usage
// [share] 
function sharelinks( $atts ){

$links = get_transient('share_links' . get_the_ID() );
if($links === false){

// add the links you want
$links = "<a href='https://www.facebook.com/sharer.php?u=" . urlencode( get_permalink() ) . "&t=" . get_the_title(). "'></a>";

// save the count for 30 days
set_transient('share_links'  . get_the_ID() , $links, 60*60*24*30 );
}
// return the response. **do not echo**
return $links;
}
// set shortcode 
add_shortcode( 'share', 'sharelinks' );

shortcode example with settings

// shortcode usage
// [my_gallery id="1" height="300" width="200"] 

function shortcode_gallery( $atts ){
// get the values from inside the shortcode and make them variables
// the id, height and width have the default settings inside the array if not set
// in the shortcode. 
extract( shortcode_atts( array(
		'id' => '1',
		'height' => '100',
                'width' => '60',
	), $atts ) );

$gallery = get_transient('gallery' . $id . $height . $width );
if($gallery === false){
//
//  Do the code that creates your gallery and return the output to a variable called Gallery
$gallery = get_my_gallery($id, $height, $width);

// save the count for 30 days
set_transient('gallery' . $id . $height . $width, $gallery, 60*60*24*30 );
}
// return the response. **do not echo**
return $gallery;
}
add_shortcode( 'my_gallery', 'shortcode_gallery' );

shortcode to show facebook follow count

 
// shortcode usage
// [fans]
function facebook_fans( $atts ){
$fans = get_transient('facebook_fans');
if($fans === false){
// put in your own facebook ID
$page_id = "YOUR PAGE-ID";
// get the XML from facebook with the response
$xml = @simplexml_load_file("http://api.facebook.com/restserver.php?  method=facebook.fql.query&query=SELECT%20fan_count%20FROM%20page%20WHERE%20page_id=".$page_id."") or die ("a lot");
// get the fan count
$fans = $xml->page->fan_count;
// save the count for 1 day
set_transient( 'facebook_fans', $fans, 60*60*24 );
}
// return the response. **do not echo**
return $fans;
}
// set shortcode 
add_shortcode( 'fans', 'facebook_fans' );

The only trick to making this work really is having a variable that you can add to a word stem. As with the share links I used the post id to set it seperate, with the gallery I used the id, height and width as this will give full caching no matter how many different ways the shortcode is used.

More from the Website Speed series…

Website Speed Part 1: Write More Efficient CSS →
Website Speed Part 2: Working With and Optimizing Images for the Web →

Author: (13 Posts)

Andy Killen has been working exclusively with the web since 1994 when he was Intel's internet engineer for Europe. Today he runs his own company phat-reaction.com as well as is the CTO of Speckyboy and was also part of the Adobe Fireworks CS6 beta test team. When Andy is not busy making websites, themes and plugins or giving training on website performance, wordpress or node.js, he can be found enjoying life in Amsterdam.

Comments