1

I am using hook_block_list_alter and hook_block_view_alter to force caching upon a block provided by nice_menus, which has bad performance when used with my HUGE taxonomy menu. Here is an abstract of my code:

/**
 * Performance hack for nice_menus
 * Force the nice menu blocks to be cached globally.
 * Note that the same menu will be displayed for all users on all pages where the block is enabled.
 */
function mymodule_block_view_alter(&$data, $block) {
  $deltas = variable_get('mymodule_nice_menus_performance_hack_deltas',array());

  if ($block->module == 'nice_menus' && in_array($block->delta,$deltas)) {
    // if we are here, then the block has been rendered. set the cache.
    cache_set('mymodule_nice_menu_'.$block->delta.'_data', $data, 'cache_block', CACHE_TEMPORARY);
    cache_set('mymodule_nice_menu_'.$block->delta.'_block', $block, 'cache_block', CACHE_TEMPORARY);
  }
}

function mymodule_block_list_alter(&$blocks) {
  $deltas = variable_get('mymodule_nice_menus_performance_hack_deltas',array());

  foreach($blocks as &$block) {
    if ($block->module == 'nice_menus' && in_array($block->delta,$deltas)) {
      $cached_data = cache_get('mymodule_nice_menu_'.$block->delta.'_data', 'cache_block');
      $cached_block = cache_get('mymodule_nice_menu_'.$block->delta.'_block', 'cache_block');
      if (!empty($cached_data) && !empty($cached_block)) {
        $block = $cached_block->data;
        $block->content = $cached_data->data['content'];
        $block->subject = $cached_data->data['subject'];
      } // else just don't set anything, and nice_menus will recreate it! 
    }
  }
  dpm($blocks);
}

Now my problem is, that whenever $block->content is set, the block's weight is ignored, and it is rendered as the first block in the region, even though $block->weight was set to the correct value.

What causes this, and how does one enforce the weight?

2
  • I think what is going on is that when you are setting $block = $cached_block->data; in mymodule_block_list_alter() it is clearing out the $block->weight value since that's not part of the data returned by hook_block_view() but rather part of the information from hook_block_info() So, I'd try just setting $block->content etc here without the initial reset of $block.
    – Jimajamma
    Commented Dec 24, 2013 at 16:26
  • That is not the case. I checked with dpm() that $block->weight is set in the cached data as well. Commented Jan 1, 2014 at 11:41

1 Answer 1

1

Symptoms: When the block is not loaded from cache, but from the database, the ordering works fine. When loading from cache, the weight is ignored.

Diagnosis: The block module uses SQL to order the blocks:

function _block_load_blocks() {
  global $theme_key;

  $query = db_select('block', 'b');
  $result = $query
    ->fields('b')
    ->condition('b.theme', $theme_key)
    ->condition('b.status', 1)
    ->orderBy('b.region')
    ->orderBy('b.weight')
    ->orderBy('b.module')
    ->addTag('block_load')
    ->addTag('translatable')
    ->execute();

// ...

After that, it ignores the weight value, and relies on the order of php array keys (order in which foreach iterates) when rendering:

function _block_get_renderable_array($list = array()) {
  $weight = 0;
  $build = array();
  foreach ($list as $key => $block) {
    $build[$key] = $block->content;
    unset($block->content);

// ...

    $build[$key] += array(
      '#block' => $block,
      '#weight' => ++$weight, // HERE LIES THE MADNESS!
    );
    $build[$key]['#theme_wrappers'][] ='block';
  }
  $build['#sorted'] = TRUE;
  return $build;
}

Strategy: Either hack the blocks module (DON'T) or find a workaround. I looked through the block.module and found (using lots of dpm()) where the order of array keys are being reversed:

function _block_render_blocks($region_blocks) {

// ...      

  foreach ($region_blocks as $key => $block) {
    // Render the block content if it has not been created already.
    if (!isset($block->content)) {
      // Erase the block from the static array - we'll put it back if it has
      // content.
      unset($region_blocks[$key]); // THIS IS WHY THE ORDER OF KEYS CHANGE

// ...

      if (isset($block->content) && $block->content) {

// ...

        $region_blocks["{$block->module}_{$block->delta}"] = $block; // THIS IS WHY THE ORDER OF KEYS CHANGE
      }
    }
  }

  return $region_blocks;
}

Workaround: Manually sort the render array in a hook_page_alter(&$page) implementation.

function mymodule_page_alter(&$page) {
  if (!empty($page['sidebar_first']['my_block_1'])) {
    $page['sidebar_first']['my_block_1']['#weight'] = -1; // move it to the top
    $page['sidebar_first']['#sorted'] = FALSE; // indicate that it still needs to be sorted before rendering
  }
}

I only have two blocks in sidebar_first, of which the bottom one is cached. So I only need to lift the other block ('my_block1') above the cached block. A more sophisticated approach will be to query the weights from the block table again.

I'm not sure whether I should consider this to be a bug in the block module. It is a shortcoming, but it's only exposed because of my custom caching hack.

0

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.