Drupal is an incredibly flexible piece of software, but that flexibility often comes at the price of slow performance. After several years working on Seltzer CRM, which I modeled after Drupal 6, I've realized that there is a simple way to get the same flexibility without as much of a hit to performance.
Drupal achieves its flexibility through modules and hooks. A Drupal module is simply a self-contained chunk of code that can be installed and enabled to provide a set of features, like a shopping cart, or comment moderation. A hook is a function in one module that changes the behavior of another module, like taking a form produced by one module, and altering its contents. Drupal's takes an implicit approach. If I have a module foo
and I want it to alter forms produced by other modules, I implement the hook_form_alter()
hook like so:
function foo_form_alter(&, &, ) { ... }
Whenever the form system creates a new form, it checks each module to see if hook_form_alter()
is implemented, and if it is, passes the form data through that function. All my module needs to do is define the function. So what's the problem?
The performance problem is in plain sight. Every time a hook is called, every module needs to be checked for the existence of that function. The number of checks increases linearly with the number of modules, and in practice, most of those checks are unnecessary because any given hook will only be implemented by a small fraction of available modules. We can improve performance by switching to an explicit approach.
For our first try, let's invent a function called form_alter_register()
that allows our module to explicitly register a callback:
form_alter_register('foo_form_alter');
function foo_form_alter(&, &, ) { ... }
Now, the form system can check each callback in the list rather than checking each module, which could speed things up quite a bit!
However, we're not quite at the same level of flexibility yet. Let's imagine that there's another module called "widget" and if its installed we want to be able to alter widgets as well. We do the following, right?
widget_alter_register('foo_form_alter');
function foo_widget_alter(&) { ... }
It works fine if the widget module is enabled, but what if the widget module is optional? Then widget_alter_register('foo_form_alter')
won't be defined, and our code will crash! We could check whether the function exists before calling it, but that could get tedious. Better yet, we can use the same trick again, resulting in something like a publication-subscription approach.
Specifically, we define two core functions, one that allows modules to register the hooks they implement, and one that calls all hooks of a given name:
function hook_register(, ) { ... }
function hook_invoke_all() { ... }
Then our code to implement the hook looks like this:
hook_register('foo', 'widget_alter');
function foo_widget_alter(&) { ... }
Now we have improved performance and the same level of flexibility. Some might argue that even calling hook_register()
before each hook definition is tedious, and it may be. On the other hand, code is read more than it is written (as the adage goes) and this pattern explicitly identifies a hook as such, making it more readable, while Drupal's current pattern does not. So for improved performance and readability, the next time I'm writing a modular framework, I'm likely to use this pattern instead.
PS: Does anyone know if there is a proper design pattern name for either of these approaches?