Let Template.php Eat Static

04.14.2007

 My arch-nemisis is overly complex logic in template.php and page.tpl.php files. It seems to me that when a drupal codebase becomes brittle and unmaintainable, the culprit is usually going to be hundreds of conditional lines of php code in a template file. You've probably seen code like this before:

THEMES/SPAGETTI_MONSTER/TEMPLATE.PHP

<?php

function spagetti_monster_page($content) {
 
// determine weather the page is a node view, or edit
 
switch(arg(0)) {
    case
'node':
      if (
is_numeric(arg(1)) && (!arg(2) || arg(2) == 'view')) {
       
$body_class = 'node_view';
      }
      else if (
arg(1) == 'add' || arg(2) == 'edit') {
       
$body_class = 'node_compose';
      }
     
// f#ck it, the clients will never look at these pages....
     
else {
       
$body_class = 'node_wtf';
      }
      break;
    case
'user'
      if (
is_numeric(arg(1)) && (!arg(2) || arg(2) == 'view')) {
       
$body_class = 'user_view';
      }
     
//.... and so on and so on and so on.....
     
break;
  }
}
?>

The dangers of this approach are something that cannot be explained: they must be felt first hand.

Switching args in a template.php or page.tpl.php file is like doing drugs (minus the laughs): lots of people have done it, survived, and even learned from it; but its an experience that is best avoided if you can help it. So lets see how to avoid it using static variables. (warning, you'll need to use a module).

SPAGETTI_MONSTER_KILLER.MODULE

<?php
function spagetti_monster_killer_set_body_class($class = NULL) {
 
// set the variable to static
 
static $set_class;
 
// if a class has been given, then go ahead and set it
 
if ($class) {
   
$set_class = $class;
  }
  if (!
$set_class) {
   
// return a default class
   
$set_class = 'random_page';
  }
 
// now return it
 
return $set_class;
}

function spagetti_monster_killer_nodeapi(&$node, $op, $teaser, $page) {
  switch (
$op) {
    case
'view':
      if (
$page == TRUE) {
       
// indeed..... this is clearly a page view of a node -- set the template.php body class 'node_page'....
       
spagetti_monster_killer_set_body_class('node_page');
      }
      break;
  }
}

function spagetti_monster_killer_form_alter($form_id, &$form) {
  if (
$form['#id'] == 'node-form') {
// we are absolutely certain we are viewing a node form... gee-wiz
   
spagetti_monster_killer_set_body_class('node_edit');
  }
}
?>

Its not hard to see why this approach allows more freedom, more maintainability, more modularity, less maintaince, and less opprotunity to make a mistake. Its likely to be a more efficent too since the class just gets set when certain functions execute (such as node_view($node, FALSE, TRUE)), instead of having to walk through a long switch statement. Using this method, all you have to have in template.php is this:

<?php
function spagetti_monster_page($content) {
 
// simply use a null vallue so that the function returns whatever was last in memory. In otherwords, anywhere this function ran to generate a page will determine the value. Give static a try....
 
$body_class = spagetti_monster_killer_set_body_class(NULL);
}
?>

A final note that these examples scratch the surface of this concept. Many people will perhaps shun such an approach -- I'd like to hear their reasons. I am, after all, an idiot.

Comments

Hi, This looks like a great

Hi,

This looks like a great code but I'm searching the Net for answers because I'm not that good at PHP yet.

Can you please give some more examples of ways to target specific pages?

I need to add a classname to the body when a view called 'services' is being viewed.

Thanks!

Similar Approach

I was just recently shown a similar approach of using a static to simplify my template.php code. While this function still requires looking at args() is does simply my other logic.

<?php
function circ_get_node_type() {
  static
$type;
 
  if (!
$type) {
    if (
arg(0) == 'node' && is_numeric(arg(1))) {
     
$node = node_load(arg(1));
     
$type = $node->type;
    }
    else
     
$type = '';
  }
 
  return
$type;
}
?>

Then in other template.php theme overrides and what not.

<?php
function circ_breadcrumb($breadcrumb) {
  if (
circ_get_node_type() == 'file' || circadia_get_node_type() == 'folder')
   
$variables = array('breadcrumb' => implode('/', $breadcrumb));
  else
   
$variables = array('breadcrumb' => implode(' :: ', $breadcrumb));
  return
_phptemplate_callback('breadcrumb', $variables, array('breadcrumb'));
}
?>

I see good uses for your approach as well.

A patch for core in 6.0

I have a core patch for Drupal 6 to add this kind of feature so that a lot of the grunt work of doing this will be automagic.

It doesn't, however, have a great deal of traction yet. Maybe you can help!

http://drupal.org/node/113382

Some minor notes. You want

Some minor notes. You want if (isset($class)) the default value can be set as static $stored_class = 'default'; and I usually prefer to have a get function function spagetti_monster_killer_set_body_class() {return spagetti_monster_killer_set_body_class();}

If you change the setter later then the you do not need to change the get. It helps.

Otherwise yes, you shall do this.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options