Making the Switch: Better theme preprocessors in Drupal 7

The switch statement is a common control structure in programming languages. You will have undoubtedly seen a few if you spend any time working with Drupal modules or themes. Switch statements love to hangout with a theme’s template.php file in preprocess hooks. Consider, if you will, the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
/**
 * Implements hook_preprocess_node().
 */
function mytheme_preprocess_node(&$vars) {
  switch ($vars['node']->type) {
    case 'article':
      // Do some article stuff.
      break;

    case 'page':
      // Do some page stuff.
      break;

    // Etc, etc, and so forth.
  }
}
?>

This is a simple way to do different processing depending on the content type of a node. You can stop here if you are working on a small site with only a few content types. Large projects, on the other hand, quickly delves into chaos as multiple developers make edits and additions. Soon you have a big blob of leftover spaghetti (all those breaks and control structures are no easier to read than a bunch of GOTOs). Take a look at the following mess:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
/**
 * Implements hook_preprocess_node().
 */
function mytheme_preprocess_node(&$vars) {
  switch ($vars['node']->type) {
    case 'article':
      if (...) {
        // Do some stuff
      }
      // Do some more stuff.
      foreach (...) {
        // Loop all the things.
        if (...) {
          // Just kill me now.
        }
      }
      break;

    case 'page':
      if (... &&...) {
        // Do some stuff.
      }
      break;

    // Etc, etc, and so forth.
  }
}
?>

As you can see, switch statements are difficult to follow when there are more than 5-10 lines per case. It becomes an even bigger mess when control structures are nested within a case option. But do not fear! We can simplify things significantly by placing the complex code from each case into separate functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
/**
 * Implements hook_preprocess_node().
 */
function mytheme_preprocess_node(&$vars) {
  switch ($vars['node']->type) {
    case 'article':
      mytheme_do_stuff_to_article($vars);
      break;

    case 'page':
      mytheme_do_stuff_to_page($vars);
      break;

    // Etc, etc, and so forth.
  }
}

function mytheme_do_stuff_to_article(&$vars) {
  if (...) {
    // Do some stuff.
  }
  // Do some more stuff.
  foreach (...) {
    // Loop all the things.
    if (...) {
      // Not so bad now.
    }
  }
}

function mytheme_do_stuff_to_page(&$vars) {
  if (... && ...) {
    // Do some stuff.
  }
}
?>

Now each case in the switch statement calls a new function to handle all the messy stuff. This is much easier to read and follow. Whenever a content type is added, just add a new case and function. But don’t stop reading! We can do better!

A lazy coder would be justified in asking, “I have 50 content types! Why must I add a new case to the switch statement for every content type?” Let’s simplify this even more. Drupal is built on a hook system that defines specific naming conventions for certain functions. It is helpful to match those same conventions in custom code whenever possible. Drupal provides hook_preprocess_node(). We could provide a unique preprocess hook for each content type. This hook might look like hook_preprocess_node__type() where type is the machine name of the content type. Now we can follow the Drupal style and remove the switch statement completely.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
/**
 * Implements hook_preprocess_node().
 */
function mytheme_preprocess_node(&$vars) {
  // Implement a preprocess hook based on node type.
  $function = 'mytheme_preprocess_node__' . $vars['node']->type;
  if (function_exists($function)) {
    $function($vars);
  }
}

/**
 * Implements hook_preprocess_node__type() for article.
 */
function mytheme_preprocess_node__article(&$vars) {
  if (...) {
    // Do some stuff.
  }
  // Do some more stuff.
  foreach (...) {
    // Loop all the things.
    if (...) {
      // Not so bad now.
    }
  }
}

/**
 * Implements hook_preprocess_node__type() for page.
 */
function mytheme_preprocess_node__page(&$vars) {
  if (... && ...) {
    // Do some stuff.
  }
}
?>

With this method, simply add an implementation of hook_preprocess_node__type() anytime you need to work on a new content type.

Tweet

Comments:

Subscribe & Contact

Know how to listen, and you will profit even from those who talk badly.
~ Plutarch ~