Building an Object-Oriented WordPress Plugin (xkcd-shortcode) – Part 5

This is Part 5 of a Tutorial Series on building an Object-Oriented WordPress plugin from scratch.

Series Navigation:

  1. Design
  2. Making Remote Requests
  3. Generating HTML
  4. Connecting to WordPress’s Shortcode API
  5. Implementing Caching

At the end of Part 4, I hinted at an important problem with the design of this plugin. Every time a page with an embedded [xkcd] shortcode is requested, WordPress will need to expand the shortcode. This means for every such request you are hitting the XKCD.com servers to fetch the comic’s JSON.

In other words the speed of your website is now directly linked to how quickly the XKCD servers respond to your JSON API request. If during peak hours, XKCD.com is being swamped with visitors, your servers are also going to feel the effect.

A 5 second delay to get the JSON from XKCD.com translates to your site also taking 5+ seconds to show up. Your server is basically spinning it’s wheels for these 5 seconds until the remote servers come back with a response.

Caching Remote Requests

To get around this problem you need to store the JSON retrieved from XKCD.com in your database. Before fetching any comic’s metadata, you must first look for it locally.

WordPress has the Transients API to allow plugins to store temporary data. You can make use of this API to store each xkcd comic by it’s number in the format xkcd-shortcode-{comic-number}.

For the latest comic the comic-number isn’t known until after the request is completed. Instead you will use xkcd-shortcode-latest with a shorter cache duration to ensure it does not become stale.

Designing a Caching layer

The XKCDLoader class makes the HTTP requests to the XKCD.com servers. But if you start baking caching into the XKCDLoader class itself, it will quickly become complicated. Even though you want to cache the results of fetching a comic, you do not want to get involved in how the original data is actually loaded.

Further the different combinations with and without caching are going to make for unnecessarily complex tests.

What you need is something to sit between the XKCDLoader and XKCDShortcode objects. When a request is made that object would check if the comic’s metadata is cached locally. If yes, the metadata is returned immediately without making any API requests.

This object is CachedXKCDLoader. It uses the same XKCDLoader to make the remote requests, but it does some local lookups first. Thus ensuring that the comic loads faster if it’s cached.

In Object-Oriented terminology this is called a Decorator. It enhances our loader with caching without extension. To make this work all we need to do is implement a custom load method with the same signature as XKCDLoader.

Implementing CachedXKCDLoader#load

By now you will have started to see a pattern how to go about this. No tempting bloated functions are allowed! Instead you need an outline.

function load($num = null) {
  /* check if num is null comic is latest */

  /* convert $num into a option key */

  /* check if option key is saved in database */

  /* if yes return the cached option value */

  /* else go fetch the json from the server */

  /* then save the json to the server */

  /* return this json */
}

Here is the sequence diagram that illustrates this. Some of the helper conditionals have been left out for brevity.

cached_xkcd_loader_sequence_diagram

Setting up CachedXKCDLoader

First lets get some configuration out of the way. You will pass the XKCDLoader object to the CachedXKCDLoader. This needs to be saved for further use.

function __construct($loader) { // |__
  $this->loader = $loader;
}

Helpers for CachedXKCDLoader

A useful helper you will need is the key helper that builds the xkcd-shortcode-{comic-number} cache storage key. Since null implies the latest comic, you can use xkcd-shortcode-latest as it’s key.

function key($num) {
  if (is_null($num)) {
    return 'xkcd-shortcode-latest';
  } else {
    return "xkcd-shortcode-$num";
  }
}

A similar helper is the duration helper. This helper will provide the duration to cache the data for a comic. You will cache numbered comics upto a week and the latest comic for 24 hours.

function duration($num) {
  if (is_null($num)) {
    return 60 * 60 * 24;
  } else {
    return 60 * 60 * 24 * 7;
  }
}

Loading a Comic And Saving it’s JSON

Next up is the helper, load_and_save. You will load the JSON using the local XKCDLoader object. Then store that JSON as a WordPress transient using WordPress’s set_transient API function.

function load_and_save($num) {
  $json = $this->loader->load($num);

  set_transient(
    $this->key($num), $json, $this->duration($num)
  );

  return $json;
}

Bringing it together in CachedXKCDLoader#load

Putting it all together is easy with the helpers in place. First you check if a comic is cached using the get_transient API function. Else the comic data needs to be loaded from the remote XKCD.com servers and saved locally for subsequent requests.

function load($num = null) {
  $json = get_transient($this->key($num));

  if ($json !== false) {
    return $json;
  } else {
    return $this->load_and_save($num);
  }
}

That completes the CachedXKCDLoader. It has the same public interface as XKCDLoader and is now ready to be swapped into place in the XKCDShortcode class.

Replacing the XKCDLoader with CachedXKCDLoader

By keeping the API identical your CachedXKCDLoader can cleanly replace the loader in the XKCDShortcode class. All tests should work with zero changes.

function __construct() { // |__
  $this->loader = new CachedXKCDLoader(new XKCDLoader());
}

That’s it! You did not need to touch the rest of the plugin. You now have a caching layer in place. Page requests to your site are no longer bound to how responsive the XKCD.com servers are.

Source Code

The source code for this plugin is available on GitHub. The plugin itself is also on the wordpress.org repository.

An aspect of this plugin that I have glossed over is testing. Object-oriented plugins tend to be a lot nicer to test. This plugin is no exception. You will find a companion test suite to go along with the source code on GitHub.

If TDD is also of interest to you, checkout wp-cli’s Plugin Unit Tests. Setting up a test suite is as simple as,

$ wp scaffold plugin-tests my-plugin

Concluding Thoughts

Through out this tutorial I have stressed the need to avoid building bloated functions. Such functions are also much harder to test. This difficulty is rarely linear, the greater the bloat, the tests will usually be an order of magnitude more difficult to write. Try to avoid writing bloated functions when you can.

If you made it this far. I’d love to hear from you. Please drop me note in the comments or on twitter. And if you found any mistakes in the code, do send me a Pull Request on GitHub!

Level Up your WordPress-Fu with Weekly Tips in your inbox. Find out just why query_posts() is evil and why you should stop using it. (No Spam)

  • Gal Anonim

    Great article chain. It not only shows some tricks on how to build object oriented wordpress plugin, but also teaches some good habits (short functions, immutability where possible, considering caching).