Module and Theme Name Collisions in Drupal
When building a new Drupal site with a custom theme and a custom module for a project, it seems reasonable to give both the same name — the name of the client, say. But this turns out to be a terrible idea, for a non-obvious but educational reason. Read on for the details.
Drupal is written in PHP, but its core code takes no advantage of the language's object oriented features. (Most of it has been around since version 4, and PHP didn't get halfway respectable OO until version 5.) So where a Java programmer would use interfaces to declare a contractual relationship between core and module, Drupal uses function naming conventions and the "hook" API.
For example, let's say we're working on a project called Tracker, and we need to create a custom Drupal block. Instead of relying on an interface Block or what have you, instead we implement the hook_block method, by replacing the word hook with the name of our module, tracker:
function tracker_block($op = 'list', $delta = 0, $edit = array()) { if ($op == 'list') { $block[0]['info'] = t('Tracker Block'); return $block; } else if ($op == 'view') { $block['subject'] = t('Trackers'); $block['content'] = ... // implementation stuff here return $block; } }
The presence of a function named tracker_block in the module is the poor man's way of saying that we provide block functionality; and passing in different strings in $op is roughly analagous to the core Drupal code calling methods on an interface. (Of course, relying on strings for these op codes is problematic in a number of different ways, but that's a post for another day.)
This is fine as far as it goes. Now let's write a custom theme for the same project, tracker, and name it, say, tracker. While this seems harmless — wise, even — in fact it turns out to be a terrible, horrible, no-good, very bad idea. It's a lousy idea because the themeing engine relies on naming conventions, as well, and you end up with a naming collision.
When it comes time to render blocks into HTML for display, Drupal calls a function theme_blocks, which is part of core. Here's what that code looks like:
function theme_blocks($region) { $output = ''; if ($list = block_list($region)) { foreach ($list as $key => $block) { // $key == <i>module</i>_<i>delta</i> $output .= theme('block', $block); } } // Add any content assigned to this region through drupal_set_content() calls. $output .= drupal_get_content($region); return $output; }
The magic happens in line 7. For each block, Drupal calls the theme function, passing in the string block for the "class" of object to theme, and then the block object itself as the "object" to render. The theme method itself is pretty confusing — you can see for yourself if you don't believe us — but what it does is decide, at runtime, which piece of code should be called to actually generate the HTML that eventually shows up on the user's browser.
One of the checks the theme function makes is to look for a function to render a block in the currently active theme. The name of this function? themename_classname — or, in this example, tracker_block. This, sadly, is the same name of our hook_block implementation shown above. Drupal calls that method instead of the function that actually generates HTML for the block. The result of this is that no blocks at all appear on your site. It's a very odd bug.
The solution to this problem is trivial: just don't name your custom theme and module the same thing! Switch to trackertheme or the like and Drupal won't get confused.
This deferral of the rendering until the last possible moment is, in fact, what gives Drupal's theming engine its mojo. It's one of the reasons we love Drupal. But PHP, well... that's not one of the reasons we love it.
Comments
Thanks!
you just save my a$$ :-)