Getting Started With Cache Functions In Drupal 7

When generating markup in Drupal you'll often want to store the output in a cache instead of regenerating it every time. This is especially important for potentially expensive rendering tasks that don't change between page requests. Drupal 7 comes with a cache system that can be taken advantage of with the cache_get() and cache_set() functions. There is also a third function called drupal_static() that also fills in gaps between these two functions.

Caching can be used for all sorts of situations, and because it is built into Drupal you can also rely on the cache management functionality that Drupal has. The basic rule of thumb is never to regenerate anything that can be cached and returned instead. This is especially important if generating the output is a time consuming and expensive task. A good example of output that can be cached easily is a news feed that is updated once day. In this case the output only needs to be regenerated when the content is updated and can be kept for a while before needing to be regenerated again. You can also cache more complicated things like search results or the facets on a search page, both of these things can be time consuming to generate and yet don't tend to change much from page request to the next.

Static Cache In Drupal

The drupal_static() function works by creating a global cache that persists for the duration of the page request and is used all over Drupal core. This is useful if you are calling the same function several times during the request but not expecting the output to change. A prime example of this in action is when loading a node that needs to be displayed in different areas of the page, not uncommon if the node appears on the page and in a block of related links. Obviously, once Drupal has loaded a node there is little gained from loading it a second time and so the drupal_static() function is used to store that the data in the static cache and retrieve it again if needed. Here is a really simple example of drupal_static() in action.

function my_module_load() {
  static $my_module_data;
  $my_module_data = &drupal_static(__FUNCTION__);
  if (empty($my_module_data)) {
    // Run expensive actions and add the data to the $my_module_data variable.
  }
  return $my_module_data;
}

The __FUNCTION__ seen in the above code is a PHP magic constant and represents the function name. The drupal_static() function takes a single required parameter, which is the name of the cache item to store. In the above code the __FUNCTION__ parameter can be replaced with 'my_module_load' to maintain the same functionality. If you want to be more specific with this tag you can concatenate this with arguments so that you are only storing and retrieving specific items in the cache.

function my_module_load($id) {
  static $my_module_data;
  $my_module_data = &drupal_static(__FUNCTION__ . $id);
  if (empty($my_module_data)) {
    // Run expensive actions and add the data to the $my_module_data variable.
  }
  return $my_module_data;
}

An alternate approach is to add a block at the top of the function to immediately return the static cache (if it exists) rather than the if statement wrapping the contents of the function.

function my_module_load($id) {
  static $my_module_data;
  $my_module_data = &drupal_static(__FUNCTION__ . $id);
  if (!empty($my_module_data)) {
    return $my_module_data;
  }

  // Run expensive actions and add the data to the $my_module_data variable.
  return $my_module_data;
}

Remember that you can call this function multiple times during the page request, but you'll always receive the same return value. It's only on subsequent page requests that the value will change if your data is different.

If you need to clear out the static cache you can run the drupal_static_reset() function, passing it the name of the static cache that is to be cleared. If you used the __FUNCTION__ magic constant you would exchange this for the name of the function you defined the cache in.

drupal_static_reset('my_module_load');

A More Permanent Cache

Static caching is fine, but real improvements in speed can be seen when you allow the cached content to be saved to disk (or persistent cache) and read on subsequent page views. This is where cache_set() and cache_get() come into play.

The cache_set() function is the most complex of these two functions and is used (as the name suggests) to write any data to the cache. This function takes a number of parameters:

  • $cid : (Required) The first parameter in the cache_set() function is the name of the cache, which is used to retrieve the cache again.
  • $data : (Required) The second parameter is the data that you want to cache. It's important to note that complex data types (like arrays and objects) will be automatically serialized before they are stored. Any serialized data will be unserialized when returned from the cache, but you'll need to ensure that any objects you have contain __sleep() and __wake() methods to put your objects back to their original state.
  • $bin : There are a number of caches available in Drupal, which are referred to as 'bins'. The default behaviour of this function is to write the cache to the main cache bin, which by default is the table called 'cache' in the Drupal database. This can be altered to a number of different bins, but you'll probably either use the default 'cache' or the 'cache_block' for block caching.
  • $expire : This tells Drupal how long to keep the cache data around for.
    • CACHE_PERMANENT: Indicates that the item should never be removed unless explicitly told to using cache_clear_all() with a cache ID.
    • CACHE_TEMPORARY: Indicates that the item should be removed at the next general cache wipe.
    • A Unix timestamp: Indicates that the item should be kept at least until the given time, after which it behaves like CACHE_TEMPORARY.

You would typically use the cache_set() function like this.

cache_set('my_module_cache', $data, 'cache', CACHE_TEMPORARY);

The cache_get() function returns the contents of the cache and takes two parameters:

  • $cid : (Required) This is the name of the cache, which should be the same as the value you used when using cache_set().
  • $bin : This is an optional parameter that allows you to select a different cache bin to get the cache from. Again, this should be the same cache bin that you used to store the cache in cache_set().

The cache_get() function doesn't only contain the data you stored in the cache, it is an object that contains both the data you stored and some other information about that data. Here is a typical example of a cache object:

stdClass Object
(
    [cid] => my_module_cache
    [data] => text
    [created] => 1427730490
    [expire] => -1
    [serialized] => 0
)

The important part here is the 'data' parameter, which you should access to get your stored cache value. The other parameters tell you the cache ID (cid) when the cache was created (created) if the cache is currently expired (expire) and if the original value was serialized (serialized).

Here is a simple example of using the cache_get() function.

$cache = cache_get($cache_id, 'cache');
$data = $cache->data;

Of course, it's best to use these two functions together. Here is an example of a simple way in which these functions can be used to create a cached resource for complex output. I've created some simple markup here to show it in action.

function my_module_create_markup() {

  // Retrieve from cache backend.
  if ($cache = cache_get('my_module_cache', 'cache')) {
    $markup = $cache->data;
  }

  // If we don't have a cached version already then build the markup.
  if (empty($markup)) {
    $markup = '';

    // Do stuff and add it to the markup variable.
    $markup .= date('c');

    for ($i = 0; $i < 10; ++$i) {
      $markup .= pow($i, 10) . ' ';
    }
  }

  // Cache the output with the cache_id (CACHE_TEMPORARY to signify that it can be removed on cache clear).
  cache_set('my_module_cache', $markup, 'cache', CACHE_TEMPORARY);

  // Return the markup, whoever it was generated.
  return $markup;
}

When putting all of these functions together I normally include a parameter that I can use to turn on or off the cache functionality via a single config setting. This makes development much easier and allows you to automatically start using the cache on the live system once the module is active. It is also good practice to separate out the cache ID into a variable, which allows the the possibility of creating a context based cache. This means that if you want to use user input to create the cache then you don't have to modify the code too much. I also tend to use the drupal_static() function in addition to the cache functions as this provides further resilience to multiple calls to the same function.

Here is an example of a fully featured cache function.

function my_module_create_markup() {
  // Initialise the static
  static $markup;

  // Check if a cache should be used.
  $use_cache = variable_get('my_module_use_cache', TRUE);

  // Set a cache ID to store and retrieve the cache.
  $cache_id = 'my_module_cache';

  if ($use_cache) {

    // Try to retrieve from static cache.
    $markup = &drupal_static($cache_id);

    if (empty($markup)) {
      // Retrieve from cache backend.
      if ($cache = cache_get($cache_id, 'cache')) {
        $markup = $cache->data;
      }
    }
  }

  // If we don't have a cached version already then build the markup.
  if (empty($markup)) {

    // Initialize the markup variable.
    $markup = '';

    // Do stuff and add it to the markup variable.
    $markup .= date('c');
    for ($i = 0; $i < 10; ++$i) {
      $markup .= pow($i, 10) . ' ';
    }
  }

  // Cache this markup?
  if ($use_cache) {
    // Cache the output with the cache_id (CACHE_TEMPORARY to signify that it can be removed on cache clear).
    cache_set($cache_id, $markup, 'cache', CACHE_TEMPORARY);
  }

  // Return the markup, whoever it was generated.
  return $markup;
}

Drupal will automatically clear the cached data when it has expired, but you can force the cache to be cleared by using the cache_clear_all() function. This function takes three optional parameters as it is possible to call it with no parameters to clear all the caches in Drupal.

  • $cid : This is the cache ID of the cache you wish to clear. Essentially, the first parameter in your cache_set() call. You can also pass an array of cache IDs in order to clear multiple caches. The $wildcard argument will be ignored if set to NULL.
  • $bin : This is the cache bin that you used to store your cache in. This is required if you supplied the $cid parameter.
  • $wildcard : If TRUE, the $cid argument must contain a string value and cache IDs starting with $cid are deleted in addition to the exact cache ID specified by $cid. If $wildcard is TRUE and $cid is '*', the entire cache is emptied.

You would use this function in other places in your code when you knew that a certain change would effect the output of the cache. For example, you might have a section that shows certain nodes, in which case you would need to add a hook_node_presave() hook so that you can clear out the cache of that section if the user has saved a node. To clear the cache created in the above code you would use the following.

cache_clear_all('my_module_cache', 'cache');

Obviously there can be a little complexity involved in caching in Drupal, but if you design your code around the cache_set() and cache_get() functions then you can create responsive sites that store repeating data without having to regenerate everything. It is possible to extend the available cache bins in Drupal, but you are strongly encouraged to use the cache_set() and cache_get() functions to interact with all caches. Even the Views cache (which creates cache_views and cache_views_data as cache bins in Drupal) still uses cache_get() to get information out of the cache.

It is also possible to alter the cache mechanisms in Drupal to write the cache to different storage mechanisms. This is accomplished via the use of modules like APC, Redis and Memcache, but also require that those systems exist before hand.

Comments

The article presented great in serious manner. Well, this is what I'm talking about.
Permalink

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
7 + 5 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.