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



RSS Feed



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 :)
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!