Take Control of Your PHPTemplate Variables

For your templating pleasure Drupal's PHPTemplate engine makes available a cast of variables, including such favourites as $title, $content, and $node. Modules also contribute: CCK adds CCK specific template variables as well as fields to the node object, and the comments module adds an entry to the $links variable. But what happens when something isn't formatted the way you or your customer wants, or you have a project specific variable you want to add?

Introducing the _phptemplate_variables function!

The _phptemplate_variables function lives in your theme's template.php file and is called by the PHPTemplate engine after the various modules have done their thing, and before PHPTemplate actually renders your node into HTML. _phptemplate_variables provides a single, common location to all .tpl.php files for adding or changing variables.

For example, if you have 3 CCK nodes with their own template files and want to change a common variable, _phptemplate_variables lets you do so in one place instead of three. It can also be an important tool for those wanting to maintain a strong separation of logic and presentation.

The _phptemplate_variables function has two parameters:

$hook: The type of template being rendered. Examples are page, block, comment
$vars: An associative array of variables to be created for the template file. The key will become the variable name and the value will become the variable value. For example, if the key is 'title' and the value is 'Kung Fu Hustle', a variable named $title will be created with the value of 'Kung Fu Hustle'.

Return value: An associative array

An empty function:

function _phptemplate_variables($hook, $vars) {
  return $vars;
}

or

function _phptemplate_variables($hook, $vars) {
  return array();
}

Notice that you can return an empty array and it will not over-write the default variables. The returned array doesn't replace the default variable array, it's merged with the default variable array using the PHP array_merge function. As you'd expect, the returned values take precedence. This means you can override a variable in _phptemplate_variables, but you can't delete a variable.

Here is an abbreviated list, in print_r format, of the $vars content for a default Drupal 5.1 system with the Garland theme. My next article will show you an easy way to generate this list. A full listing can be found here: Full Listing of $vars for a Node

Array
(
  [nid] => 11
  [vid] => 11
  [type] => story
  [status] => 1
  [created] => 1177138763
  [changed] => 1177138763
  [comment] => 2
  [promote] => 1
  [sticky] => 0
  [title] => Story Example
  [body] => <p>This is a story node.</p>
  [format] => 1
  [uid] => 1
  [name] => <a href="/user/1" title="View user profile.">admin</a>
  [readmore] => 
  [content] => <p>This is a story node.</p>
  [date] => Fri, 04/20/2007 - 22:59
  [node] => stdClass Object
  [node_url] => /node/11
  [page] => 1
  [teaser] => 
  [terms] => 
  [submitted] => Submitted by <a href="/user/1" title="View user profile.">admin</a>
                 on Fri, 04/20/2007 - 22:59.
  [id] => 1
  [directory] => themes/garland
  [is_front] => 
)

Open up a node.tpl.php file and mentally map the $vars "[itemname]" entry to "$itemname" in the file and you'll see all of the usual suspects are there. For example, [title] to $title.

How do we make _phptemplate_variables work for us? Here are some examples. These examples assume there is no pre-existing _phptemplate_variables function in your template.php file. Some themes, such as Garland, use _phptemplate_variables. If you're trying these examples with such a template you'll need to modify the example code accordingly. Blue Marine doesn't use a template.php file or _phptemplate_variables function, so may be a better theme to experiment with if you are unsure about updating an existing function.

Example 1: Adding a node template variable

In this example we make a new variable named $permalink available in the node template.

function _phptemplate_variables($hook, $vars) {
  if ($hook == 'node') {
    return array('permalink' => '<a href="' . $vars['node_url'] . '">Permalink</a>');
  }
  return array();
}

In our node.tpl.php file, use the following PHP code to print the value:

<?php print $permalink ?>

Example 2: Adding a page template variable

In this example we want to display sponsorship information on each page, but anonymous users see something different than logged in (aka authenticated) users. We want to keep the page template as straight forward as possible and avoid filling it with decision code.

For the example let's use the following "sponsor" functions:

function example_anon_sponsor() {
  return 'This page brought to you by the number ' . rand(1,10);
}

function example_auth_sponsor() {
  return 'This page brought to you by the letter ' . chr(64 + rand(1,26));
}

Our template.php function would look something like this:

function _phptemplate_variables($hook, $vars) {
  if ($hook == 'page') {
    global $user;
    if ($user->uid == 0) {
      $vars['sponsor'] = example_anon_sponsor();
    } else {
      $vars['sponsor'] = example_auth_sponsor();
    }
    return $vars;
  }
  return array()
}

Use the following PHP code in your page.tpl.php file to print the value:

<?php print $sponsor ?>

Having seen how to add new variables you've probably already figured out changing an existing variable is simply a matter of manipulating the array values.

Example 3: Title Example

We have decided, for the sake of a highly contrived example, that each node should have a unique identifier that is displayed with it's title. Since the node id (nid) is unique within Drupal, this is what we've decided to use. The format of the title is: The Node Title (nid)

function _phptemplate_variables($hook, $vars) {
  if ($hook == 'node') {
    $vars['title'] = $vars['title'] . ' (' . $vars['nid'] . ')';
    return $vars;
  }
  if ($hook == 'page') {
    // Make sure this page is displaying a node
    if (isset($vars['node'])) {
      $vars['title'] = $vars['title'] . ' (' . $vars['node']->nid . ')';
    }
    return $vars;
  }
  return array();
}

This example demonstrates some real world considerations:

  • For most standard templates the node title is displayed by the node template (node.tpl.php) in preview/teaser view and the page template (page.tpl.php) in page view. To change the title requires a change in more than one place. Since different information is available in $vars depending on whether a page or a node is being rendered, the same statements won't necessarily work in both situations. Thus the use of $vars['nid'] for $hook == 'node' and $vars['node']->nid for $hook == 'page'. (Note: In this case I could use $vars['node']->nid for both page and node, but did it differently to highlight the point.)
  • The page template is used for all pages, not just nodes being displayed as a page. For a default configuration we need to check that the page is a node before updating the title or we'll get a pair of empty parenthesizes on the front page (because the front page isn't a node and has no title or nid). In some situations, more logic may be required. For example, if you set a story node as the front page you may not want to display a title.
  • This example only works on titles if $title is being used in the page and node templates. If the themer has changed this, the code needs to be changed accordingly.
  • When you have the same changes in two different locations, consider adding a theming function.

Here's some other information to help you in your variable changing ways:

Check for Settings

Don't use your new hammer to threat everything like a nail! There may be a setting you can change instead of directly modifying the variable. A common example is the submission information in $submitted:

[submitted] => Submitted by <a href="/user/1" title="View user profile.">admin</a>
               on Fri, 04/20/2007 - 22:59.

If you wanted to remove this you could set it to empty string, or you could go to Administer >>> Site building >>> Configure >>> Global setting and disable "Display post information" for the pages you don't want it displayed on.

Check for Theming Functions

There may be a theme function you can override instead of changing the variable. It isn't always easy to determine whether or not there is a theming function. If the variable is provided by Drupal or the PHPTemplate engine you can check the appropriate function in the phptemplate.engine file in the themes/engines/phptemplate directory to see how it is set. Determining which function to check is straight forward:

node: phptemplate_node function
page: phptemplate_page function
block: phptemplate_block function
comment: phptemplate_comment function

For example, if you wanted to theme the a node's $name variable differently you'd find this line in the phptemplate_node function:

'name'  => theme('username', $node)

This means you can create your own function to override the Drupal theme function. You'll find an example of this in the Drupal handbook: Create and display a friendly alias instead of users username.

You won't find all your answers the phptemplate.engine file. For example, variables introduced by CCK and other modules don't live there. In these situations check the module's .module file for functions starting with "theme_".

Themed & Source Data

Some items have both source and themed values present in $vars, though it isn't always obvious. Examples are links, style sheets and JavaScript files. If you manipulate the source data you may also have to regenerate the 'themed' version. I hope to cover more of this in later articles.

Remember to Handle Module State

If you're modifying a value provided by a module, remember to handle the conditions for the module being turned off. The Drupal module_exists() function can be used to test this:

if (module_exists('name_of_module_here')) {
  // Do some stuff
}

It typically isn't fatal if you don't test module state, but can be in some situations. A more common result is nonsensical output.

Security Considerations

If you haven't already, read through the appropriate sections of Writing secure code in the Drupal Handbook. In particular: Handle text in a secure fashion. Drupal anticipates filtering on output and therefore does nothing to the data when it's stored. Be aware of where the data is coming from and whether you need to filter it or not.

An exercise for the reader: Is my use of $vars['title'] in example 3 appropriate, or should there be a check_plain, too?

Answer: In as much as $title is output directly in the standard distribution templates we can assume it's safe! To check for ourselves we can see where it's created. We'd find $vars['title'] is created by the phptemplate_node function in phptemplate.engine using the check_plain function: 'title' => check_plain($node->title). Since $title is filtered before I use it, I'm ok using it directly.

In Conclusion

The _phptemplate_variables function gives you a place to centrally manage your template variables. Appropriate use can improve your template's readability and maintainability.

Comments

but could you please close your tags, it makes http://drupal.org/planet hard to read

Sorry, the dangers of adding the HTML in by hand at the end of a long day. HTML now validates at http://validator.w3.org/, so should be ok.

Very nice and usefull article.
It could be nice to commit this to drupal.org (or to post a link somewhere in forum, under "theme development")

Are there any reasons _phptemplate_variables wouldn't get called on your page? I have a site and theme that's gone through a lot of hands and I'm not getting any output from the function. Is this a function other modules might hook or intercept while Drupal's building and themeing a page?

To the best of my knowledge modules can not influence the calling of _phptemplate_variables. The theme layer is called after the modules are.

Here's some ideas off the top of my head, maybe they'll provide inspiration even if they aren't the reason. Some of them are basic, but if you've inherited it and it isn't working perhaps the problem is something simple lost in the spaghetti.

  • Check for a typo in the function name or improper parameters
  • If you're using a sub-theme, verify the template.php file you think is getting called, is actually the one that is getting called.
  • Verify there aren't multiple versions of the theme. e.g., in sites/all/theme and sites/example.com/theme. The most specific takes precedence.
  • Check that the calling parameters are correct
  • _phptemplate_variables is a Drupal 5 function. It's been replaced by preprocess functions in Drupal 6. See this page for more information: Setting up variables for use in a template (preprocess functions)

If you install the devel module, you can use the dsm function to print messages. By placing a call along the lines of dsm('_phptemplate_variables was called with ' . $hook) in the function you can verify the function is getting called.