Caching Render Arrays in Drupal 7

Versions used: 

Render arrays in Drupal are great. If you do not know what a render array is then take a look here http://drupal.org/node/930760, but basically they are arrays that convert into HTML. Render arrays are used to allow other modules to override output before going through the theme layer. What happens though if you have a render array that does a lot of processing? You do not want that array, or part of that array, running each time the page is called.

Here I describe how to cache a render array. Firstly, we need a render array:

<?php
function my_render_array() {
 
 
$my_array = array(
   
'#type' => 'markup',
   
'#markup' => 'my expensive code here',
  );
 
  return
$my_array;
}
?>

This function returns our render array, so let us say in our example that this function gets called within a hook, where we use drupal_render() to render the array.

<?php
/**
 * Implements hook_page_alter().
 */
function dev_page_alter(&$page) {
 
$rendered = drupal_render(my_render_array());
}
?>

Now lets apply some caching to our render array so the markup gets saved in Drupal's cache table.

<?php
function my_render_array() {
 
$my_array = array(
   
'#type' => 'markup',
   
'#markup' => 'my expensive code here',
   
'#cache' => array(
     
'keys' => array('module_name', 'my_render_array'),
     
'bin' => 'cache',
     
'expire' => -1,
     
'granularity' => DRUPAL_CACHE_PER_PAGE,
    ),
  );
 
  return
$my_array;
}
?>

Now the drupal_render() function knows that we want to cache our render array. What happens when the array first gets rendered is there is a call to drupal_render_cache_set(), which caches the rendered output of a renderable element, in this case, what we have in #markup. So the next time that render array goes through drupal_render(), a check is made to see if element has been cached, if so, serve that cached output instead.

There is one problem here though, our expensive code will always run because the my_render_array() function will execute before the caching happens in drupal_render(), which makes the whole thing pointless. We can test this by adding some output (assuming you have the Devel module installed):

<?php
function my_render_array() {
 
dsm(time()); // will display current timestamp
 
$my_array = array(
   
'#type' => 'markup',
   
'#markup' => 'my expensive code here',
   
'#cache' => array(
     
'keys' => array('module_name', 'my_render_array'),
     
'bin' => 'cache',
     
'expire' => -1,
     
'granularity' => DRUPAL_CACHE_PER_PAGE,
    ),
  );
 
  return
$my_array;
}
?>

Refreshing the page will show the time changing, proving the expensive code will be executing each time, even though the markup is being stored in the Drupal cache table. A way round this is to use #pre_render. We can create a function that will execute before the array is rendered, but more importantly, this function is called after drupal_render() checks the cache, so will not execute if markup is being served from the cache. Here's a full example:

<?php
/**
 * Implements hook_page_alter().
 */
function dev_page_alter(&$page) {
 
$rendered = drupal_render(my_render_array());
}

function
my_render_array() {
 
$my_array = array(
   
'#type' => 'markup',
   
'#markup' => '', // we have moved our expensive code to a pre render function
   
'#cache' => array(
     
'keys' => array('module_name', 'my_render_array'), // creates the cid
     
'bin' => 'cache', // what cache table to use
     
'expire' => CACHE_TEMPORARY, // remove on next cache wipe
     
'granularity' => DRUPAL_CACHE_PER_PAGE, // cache per page (obviously)
   
),
   
'#pre_render' => array('my_render_array_pre_render'), // make our pre render function known
 
);
 
  return
$my_array;
}

function
my_render_array_pre_render($element) {
 
$element['#markup'] = 'our expensive code here';
 
  return
$element;
}
?>

We have now successfully cached the render array element and our expensive code is processed only once, when there is no entry in the cache table, and not each time the page is called.