Want to get Lullabot article, videocast, and podcast announcements delivered right to your in-box? Let us know your email address (we won't share it) and we'll let you know when anything exciting happens.

Theming Best Practices (Garland Gets a Cleanup)

Drupal version: 6.x

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

<?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

<?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)

<?php
 
// 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] = '<span>'. $site_fields[0] .'</span>';
  }
 
$site_html = implode(' ', $site_fields);

  if (
$logo || $site_title) {
    print
'<h1><a href="'. check_url($front_page) .'" title="'. $site_title .'">';
    if (
$logo) {
      print
'<img src="'. check_url($logo) .'" alt="'. $site_title .'" id="logo" />';
    }
    print
$site_html .'</a></h1>';
  }
?>

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

<?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] = '<span>'. $site_fields[0] .'</span>';
  }
 
$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

<?php
function phptemplate_breadcrumb($breadcrumb) {
  if (!empty(
$breadcrumb)) {
    return
'<div class="breadcrumb">' . implode(' › ', $breadcrumb) . '</div>';
  }
}
?>

After:
template.php

<?php
function garland_breadcrumb($breadcrumb) {
  if (!empty(
$breadcrumb)) {
    return
'<div class="breadcrumb">' . implode(' › ', $breadcrumb) . '</div>';
  }
}
?>

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.

Comments on this post will automatically be closed three months from the original post date.

Comments

Thanks for the great write-up

It's wonderful having Lullabot on my team! My sites just got a little better.
Thanks.

what about $logo but !$site_name?

The example's cleanup of the logo, site title and slogan code has a flaw. What happens if you have a logo but not a site title?

Otherwise, if a site title is required (which it doesn't need to be) then why have the first condition?

Good Point, Another Bug?

The cleanup patch that got in didn't change any of the logic, it just moved it from page.tpl.php to template.php. If this is indeed a bug, it's one that's been there since Drupal 5 was released. If you can create a scenario where Garland improperly prints out the logo/title, we should open a new issue on Drupal.org.

Very good article

Dude, this is exactlly what I was looking for write an article about best praticies on Drupal theme development for guys on Brazilian Drupal Community.

Thanks a lot!

Nicely presented

Thanks for writing this up. A positive code improvement translated into a learning opportunity for all.

Great writeup

Thanks for taking the time to write this up. It's always great to have a reminder of the best way to do things in your themes.

Thanks!

Wild, I was just thinking about theme best practices and here you are with the answers -- just in time again!

cheers,

g

Great to see

Oh good, now I can tell people to actually look at Garland's code. :)

Thanks for this (short but

Thanks for this (short but useful) article about Drupal theming best practice.
Didn't know that Garland has been "purified".

preprocess hook?

When does garland_preprocess_page() get called in your example? Is this a new hook?

Preprocess is new in Drupal 6

In Drupal 5, there's only one function for overriding variables: _phptemplate_variables(). Drupal 6's identical version of this same function is hook_preprocess(). However, because the _phptemplate_variables function tended to get very (very) large, in Drupal 6 you can now target any theme function before it get's sent to a template, so you can also do things like garland_preprocess_node(), or garland_preprocess_block(), or anything else that uses a .tpl.php file.

Great!

Thanks for this, I was recently thinking about Theming Best Practices internally and this is an excellent resource.

.sidebars vs .two-sidebars

Hey Nate,

Thanks for the post. I'm working on a Garland-based theme right now, so I was able to make use of this immediately. I might mention that the class used for body in the old Garland when both sidebars are displayed is "sidebars", whereas the new class is "two-sidebars".

So if you're trying to roll these changes into a custom Garland theme, don't forget to update your style.css (and print.css) to replace ".sidebars" with ".two-sidebars"!

Cheers,

Nick

Thanks

I should mention that this change was also included in the patch submitted to core, you can grab the patch itself from the issue on d.o.:

http://drupal.org/files/issues/garland_page_cleanup_2.patch

Themer module

The themer module (D.O/project/themer) provides body class functionality which many users may find easier to use. These include path, aggregated paths, roles, locale, random, etc

phptemplate_ prefix

why does garland use the phptemplate_ prefix ? oh why ?

also, remember http://drupal.org/node/55126 advocates its usage as well :)

<?php
 
function chameleon_item_list($items = array(), $title = NULL, $type = 'ul') {
?>
This is not actually the preferred method of overriding, however, but it is the one that always works. Most themes, however, are derived from one of the Template Engines. They can override these functions by using the name of the template engine instead of the theme name. This is preferred, because it makes it very easy to share these functions with other users who might be using a different theme. Or even share a single file of functions with several themes on the same site!

Great tips and explanation...but can you?

Great tips and explanation but could you possibly suggest a best practice theme that we should look at? I've read about the Zen Theme but I'm not sure if its still supported for Drupal 6?

Is there a good base theme to get started with in Drupal for either version 5 or 6? I haven't built a Drupal theme yet myself, I've just read about it in the Pro Drupal Development book, I have developed theme's for various other CMS systems, just not Drupal yet.

Cheers,
Gavin

zen!

zen is definitely the way to go, and yes it's supported for D6
http://drupal.org/project/zen

For which version?

This patch is for Drupal 7 rather than 6 isn't it? Your "before" code is in Drupal 6.2.

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

NICE TUTORIAL!
Very interesting and useful informations.
This looks good! Really good tutorial include so many helpful informations!
Excellent SITE. I will refer people to your ITEMS.
Cheers

Function Prefixes

Thanks for this article, it's great. So great that we've made it 'sticky' on The Webmaster Forums. Now we don't have to repeat ourselves, just send people to this article!

Best Practices

GOOD WORK! THANKS!