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.
Comments: