drupaldrupal-7hookdrupal-templates

How does hook_theme() work?


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?


Solution

  • 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:

    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.