Theming Best Practices (Garland Gets a Cleanup)

Yesterday Garland got a long-overdue update: The page.tpl.php file was updated to use best practices. Now we can finally open up Garland in a workshop scenario and not have to use it as example of the bad practices within a .tpl.php file. This article applies to Drupal 6 and higher, though the theming principles apply to all versions of Drupal.

What's this about best practices? Let's compare the before and after of a few of the improvements. Each of the items below are extremely common things you can do to keep your .tpl.php files clean.

Avoiding Function Calls Directly in a .tpl.php file

The theme layer is a place where logic and markup should be clearly separated. The .tpl.php files should be used explicitly for markup, just printing out variables whenever necessary. Template.php can be used for any extensive stints of logic or anything more complicated that printing out a variable.

Before:
page.tpl.php

  
    <!--[if lt IE 7]>
      <?php print phptemplate_get_ie_styles(); ?>
    <![endif]-->
  

After:
page.tpl.php

  
    <!--[if lt IE 7]>
      <?php print $ie_styles ?>
    <![endif]-->
  

template.php

  
function garland_preprocess_page(&$vars) {
  $vars['ie_styles'] = garland_get_ie_styles();
}
  

The difference here is that all functions have been removed from page.tpl.php. Instead, we set a variable in template.php, then simply print out that variable in page.tpl.php. Note that this is in Drupal 6, in previous versions of Drupal the same thing would be done in the _phptempate_variables() function.

Using Drupal Core's Body Classes

In the new theming system for Drupal 6 and higher, the concept of "body classes" has become standardized. That is, the layout of the page is determined by classes set on the <body> tag. Some of these classes are things like "front", "logged-in", "page-node", and "sidebar-left". These classes allow CSS to control the layout or display things in a different way for logged in users.

Before:
page.tpl.php

  
<body<?php print phptemplate_body_class($left, $right); ?>>
  

Hompage HTML

  
<body class="sidebar-left">
  

template.php

  
function phptemplate_body_class($left, $right) {
  if ($left != '' && $right != '') {
    $class = 'sidebars';
  }
  else {
    if ($left != '') {
      $class = 'sidebar-left';
    }
    if ($right != '') {
      $class = 'sidebar-right';
    }
  }

  if (isset($class)) {
    print ' class="'. $class .'"';
  }
}
  

After:
page.tpl.php

  
<body class="<?php print $body_classes ?>">
  

Homepage HTML

  
<body class="front logged-in page-node one-sidebar sidebar-left">
  

No additional code is needed in template.php, since Drupal core now provides this $body_classes variable for us.

Moving Logic from page.tpl.php to template.php

This example is probably the worst offender in Garland's page.tpl.php. It contained a large section of PHP code, including creating an array, imploding that array, and several function calls. Moving that to template.php keeps our page.tpl.php clean and used just for what templates are made for: markup.

Before:
page.tpl.php (yikes)

  
  // Prepare header
  $site_fields = array();
  if ($site_name) {
    $site_fields[] = check_plain($site_name);
  }
  if ($site_slogan) {
    $site_fields[] = check_plain($site_slogan);
  }
  $site_title = implode(' ', $site_fields);
  if ($site_fields) {
    $site_fields[0] = ''. $site_fields[0] .'';
  }
  $site_html = implode(' ', $site_fields);

  if ($logo || $site_title) {
    print '';
    if ($logo) {
      print ''. $site_title .'';
    }
    print $site_html .'';
  }
  

After:
page.tpl.php

  
<?php if ($logo || $site_title): ?>
  <h1><a href=" <?php print $front_page ?>" title="<?php print $site_title ?>">
  <?php if ($logo): ?>
    <img src="<?php print $logo ?>" alt="<?php print $site_title ?>" id="logo" />
  <?php endif; ?>
  <?php print $site_html ?>
  </a></h1>
<?php endif; ?>
  

template.php

  
function garland_preprocess_page(&$vars) {
  // Prepare header
  $site_fields = array();
  if (!empty($vars['site_name'])) {
    $site_fields[] = check_plain($vars['site_name']);
  }
  if (!empty($vars['site_slogan'])) {
    $site_fields[] = check_plain($vars['site_slogan']);
  }
  $vars['site_title'] = implode(' ', $site_fields);
  if (!empty($site_fields)) {
    $site_fields[0] = ''. $site_fields[0] .'';
  }
  $vars['site_html'] = implode(' ', $site_fields);
}
  

Proper template.php Function Prefixes

Garland has always used "phptemplate_" as the prefix for all it's theme functions. Why? The "phptemplate_" prefix indicates a function owned by PHPTemplate (Drupal's default theme engine). Sure, this works, but Garland really should be using it's own theme prefix. It's consistent with the naming conventions used throughout all the rest of Drupal modules and themes.

Before:
template.php

  
function phptemplate_breadcrumb($breadcrumb) {
  if (!empty($breadcrumb)) {
    return '
' . implode(' › ', $breadcrumb) . '
'; } }

After:
template.php

  
function garland_breadcrumb($breadcrumb) {
  if (!empty($breadcrumb)) {
    return '
' . implode(' › ', $breadcrumb) . '
'; } }

Wrap Up

Often part of the problem with Drupal's templating system is that PHP allows you to too much in the theme layer. It's tempting to just slip that SQL query directly into node.tpl.php... but don't do it! The end result can be messy, hard to update themes. Keep as much logic as possible out of your .tpl.php files, instead move large chunks of code to template.php. This keeps potential bugs in one place, and your designers will appreciate having a .tpl.php file that's easy to understand.

Published in:

Get in touch with us

Tell us about your project or drop us a line. We'd love to hear from you!