1

When returning info for a custom block we can specify what kind of caching that will be used for the block according to the values in: hook_block_info(). Notice that if the block is cached the hook_block_view() will not be called at all. In a module I'm developing I would like the user to be able to set the expiration time for the block cache, so I have tried something like this:

/**
 * Implements hook_block_info().
 **/
function CUSTOM_MODULE_block_info() {
  return array(
    'custom_block' => array(
      'info' => t('Custom Block'),
      'cache' => DRUPAL_NO_CACHE,
    ),
  );
}

/**
 * Implements hook_block_configure().
 **/ 
function CUSTOM_MODULE_block_configure($delta = '') {
  $form = array();
  $form['custom_module_cache_expire'] = array(
    '#type' => 'textfield',
    '#title' => t('Cache expire'),
    '#default_value' => variable_get('custom_module_cache_expire', 0),
  );
  return $form;
}

/**
 * Implements hook_block_view().
 **/
function CUSTOM_MODULE_block_view($delta = '') {
  $block = array();
  if ($delta == 'custom_block') {
    $cache = &drupal_static(__FUNCTION__);
    if (empty($cache)) {
      // Try to get data from cache.
      $cache = cache_get('custom_module');
      if (!empty($cache->data)) {
        // Check the expiration time.
        if (time() > $cache->expire) {
          // Remove the cache.
          cache_clear_all('custom_module');
        }
        $block = $cache->data;
      }
      else {
        // Build the renderable array and cache the result for 5 minutes.
        // The 'custom_module_get_items()' is a long running task and
        // the reason why we want to cache this block at a certain interval.
        $block['content'] = array(
          '#items' => custom_module_get_items(),
        );
        cache_set('custom_block', $block, 'cache', time() + (60 * 5));
      }
    }
    else {
      $block = $cache->data;
    }
  }
  return $block;
}

The above almost works as expected, but I was wondering if there some better way of doing this.

In Drupal 8 we can use the cache max-age to achieve this behavior.

I also tried to set the cache in the renderable array for the block, but I'm not sure what this does actually:

$block['content'] = array(
  '#items' => custom_module_get_items(),
  '#cache' => array(
    'expire' => time() + (5 * 60),
  ),
);

Perhaps the best way would be to simply set the caching to DRUPAL_NO_CACHE for the block and then in custom_module_get_items() use cache_set() and cache_get()?

I was also thinking that another way of doing this might be to just remove the default block cache from the database at a specific interval?

3 Answers 3

3
+50

Here is a possible answer which is two-fold:

  1. Make use of Drupal's render cache through the #cache property: This seems the more 'appropriate' and 'elegant' way for caching for your case since you want to cache on the presentation layer.
  2. Purge the cache object manually (instead of periodically) by using a global timestamp variable that holds the last time the cache object was purged: This will allow you to clear only the relevant cache that interests you, instead of affecting multiple other caches.

Regarding the 1. part:

Using the render cache would mean producing a render array that looks like this:

$block['content'] = array(
  '#pre_render' => array('custom_module_prerender_items'),
  '#cache' => array(
    'expire' => DRUPAL_CACHE_CUSTOM,
    'cid' => 'custom_module_cid',
    'bin' => 'custom_module_bin',
  ),
);

This implies the existence of a custom_module_prerender_items() that is called whenever the (render) cache object is not available. That function should wrap all expensive function calls.

Regarding the 2. part:

Your CUSTOM_MODULE_block_view() function can now perform a cache clearing when needed and returns - consistently - a render array as described above. For example:

/**
 * Implements hook_block_view().
 */
function CUSTOM_MODULE_block_view($delta = '') {      
  $block = &drupal_static(__FUNCTION__, array());
  if ($delta == 'custom_block' && empty($block)) {
    // Check if it's time to clear the caches.
    $custom_module_cache_expire_cache_cleared_timestamp = variable_get('custom_module_cache_expire_cache_cleared_timestamp', -1);
    $custom_module_cache_expire_minutes_interval = variable_get('custom_module_cache_expire_minutes_interval', 0);
    if (time() - $custom_module_cache_expire_minutes_interval*60 > $custom_module_cache_expire_cache_cleared_timestamp) {
      // Remove the specific cache.
      cache_clear_all('custom_module_cid', 'custom_module_bin');
    }
    // Assemble render array.
    $block['content'] = array(
      '#pre_render' => array('custom_module_prerender_items'),
      '#cache' => array(
        'expire' => DRUPAL_CACHE_CUSTOM,
        'cid' => 'custom_module_cid',
        'bin' => 'custom_module_bin',
      ),
    );
  }
  return $block;
}

Finally, the #pre_render callback, which wraps the expensive function, could look sth like this:

function custom_module_prerender_items($elements) {
  // Expensive function call.
  $items = custom_module_get_items();
  // Further processing...
  $processed_items = _custom_module_render_items($items);

  // Constructing the render array.
  $elements['custom_content'] = array(
    '#items' => $processed_items,
  );
  return $elements;
}

N.B.: Better use DRUPAL_CACHE_CUSTOM in your block definition, similar to forum_block_info(): Semantically meaning, "We will handle caching on our own"; practically meaning, no caching applied from the block module.

Refs:

1
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Clive
    Commented May 16, 2018 at 8:43
3

There is a real live example of custom cache for drupal 7 in forums:

Asis in Core forum module:

    /**
     * Implements hook_block_view().
     *
     * Generates a block containing the currently active forum topics and the most
     * recently added forum topics.
     */

function forum_block_view($delta = '') {
  $query = db_select('forum_index', 'f')
    ->fields('f')
    ->addTag('node_access');
  switch ($delta) {
    case 'active':
      $title = t('Active forum topics');
      $query
        ->orderBy('f.last_comment_timestamp', 'DESC')
        ->range(0, variable_get('forum_block_num_active', '5'));
      break;

    case 'new':
      $title = t('New forum topics');
      $query
        ->orderBy('f.created', 'DESC')
        ->range(0, variable_get('forum_block_num_new', '5'));
      break;
  }

  $block['subject'] = $title;
  // Cache based on the altered query. Enables us to cache with node access enabled.
  $block['content'] = drupal_render_cache_by_query($query, 'forum_block_view');
  $block['content']['#access'] = user_access('access content');
  return $block;
}

Here drupal_render_cache_by_query does caching based on query result.

Prepares an element for caching based on a query.

This smart caching strategy saves Drupal from querying and rendering to HTML >when the underlying query is unchanged.

Expensive queries should use the query builder to create the query and then >call this function. Executing the query and formatting results should happen in >a #pre_render callback.

As in quote query executing and rendering happens in hook_block_pre_render() as in forum.

Another Example:

$block['content'] = array(
  '#items' => custom_module_get_items(),
  '#cache' => array(
   'cid' => 'custom_module',
   'bin' => 'cache',
    'expire' => time() + (5 * 60),
  ),
);

The above code is similar to what you did with cache_get, cache_set methods in much similar terms with less code, as drupal_render() function handles this for you setting up cache using drupal_render_cache_set().

You can read more from here: ADVANCED CACHING WITH RENDERABLE CONTENT

As for cache policy when you are caching stuff with custom code in hook_block_info DRUPAL_CACHE_CUSTOM and DRUPAL_NO_CACHE has no difference. However, using DRUPAL_CACHE_CUSTOM for custom caching is better standard.

Check out Block Caching for drupal 7.

1
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Clive
    Commented May 16, 2018 at 8:43
0

I think you have to use this for the cache:

$expire = time() + 3600;
$block['#cache'] = array(
        'cid' => 'custom_data',
        'bin' => 'cache',
        'expire' => $expire
      );
cache_set(' custom_data', $navigation, 'cache', $expire);

Hope this helps you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.