How does hook_theme() work?

Chris Muench picture Chris Muench · Oct 12, 2011 · Viewed 12.3k times · Source

I am having a hard time understanding what hook_theme() does.

My understanding is that it has something to do with making it possible to override templates.

I was looking at:

  $theme_hooks = array(
    'poll_vote' => array(
      'template' => 'poll-vote',
      'render element' => 'form',
    ),
    'poll_choices' => array(
      'render element' => 'form',
    ),
    'poll_results' => array(
      'template' => 'poll-results',
      'variables' => array('raw_title' => NULL, 'results' => NULL, 'votes' => NULL, 'raw_links' => NULL, 'block' => NULL, 'nid' => NULL, 'vote' => NULL),
    ),
    'poll_bar' => array(
      'template' => 'poll-bar',
      'variables' => array('title' => NULL, 'votes' => NULL, 'total_votes' => NULL, 'vote' => NULL, 'block' => NULL),
    ),
  );

Could you provide an example of how it works?

Answer

Clive picture Clive · Oct 12, 2011

It provides a place for a module to define its themes, which can then be overridden by any other module/theme. It will also provide the opportunity for any module to use a hook such as mymodule_preprocess_theme_name to change the variables passed to the eventual theme function or template file.

There are basically two ways to initialise a theme function:

theme('poll_results', array('raw_title' => 'title', 'results' => $results, etc...));

and

$build = array(
  '#theme' => 'poll_results',
  '#raw_title' => 'title',
  '#results' => $results,
  etc...
); // Note the '#' at the beginning of the argument name, this tells Drupal's `render` function that this is an argument, not a child element that needs to be rendered.

$content = render($build); // Exact equivalent of calling the previous example now that you have a render array.

Please keep in mind, you should avoid calling theme() directly (per the documentation in theme.inc) since it:

  • Circumvents caching.
  • Circumvents defaults of types defined in hook_element_info(), including attached assets
  • Circumvents the pre_render and post_render stages.
  • Circumvents JavaScript states information.

In Drupal 8, theme() is a private function, _theme(). For more detail, please see www.drupal.org/node/2173655.

When you compare the two of these to the poll_results element in the example you give above you can probably work out what's happening...since PHP is not a strongly typed language Drupal is providing 'named arguments' through either a keyed array passed to the theme function, or as hashed keys in a render array.

As far as 'render element' is concerned, this basically tells the theme system that this theme function will be called using a render array, with one named argument (in this case form). The code would look something like this:

$build = array(
  '#theme' => 'poll_choices',
  '#form' => $form
);

This will pass whatever's in the $form variable to the theme function as it's sole argument.

Regarding the template key:

'poll_vote' => array(
  'template' => 'poll-vote',
  'render element' => 'form',
)

defines a theme called poll_vote which uses a template file (hence the template key) with a name of 'poll-vote.tpl.php' (this is by convention). The path to that template file will be found by using the path to the module that implements it (e.g. modules/poll/poll-vote.tpl.php), so it's fine to put template files in sub-folders of the main module folder.

There are two ways to actually return the output for a theme function, by implementing the physical function name (in this case it would be theme_poll_vote) or by using a template file. If the template key is empty Drupal will assume you've implemented a physical function and will try to call it.

Template files are preferable if you have a fair bit of HTML to output for a theme, or you simply don't like writing HTML in strings inside PHP (personally I don't). In either case though, the variables passed when you call the theme (either using theme() or a render array as described above) are themselves passed through to the template file or theme function. So:

function theme_poll_results(&$vars) {
  $raw_title = $vars['raw_title'];
  $results = $vars['results'];
  // etc...
}

If you were using a template file instead for the same method the variables would be available as $raw_title, $results, etc, as Drupal runs extract on the $vars before parsing the template file.

I'm sure there's a lot I've missed out here but if you have any more specific questions ask away and I'll try to help out.