Hook ordering in Drupal
The community has taken a few stabs at the hook ordering problem. Sometimes the concept of a “hook registry” has been used. Here’s two threads I found on the issue (if anyone knows of some newer ones please let me know!):
I haven’t seen any commited code relating to hook orderig or a hook registry in Drupal 6, though I think it may have found new life in the updated menu system.
Alas, I was really hoping hook ordering of some form would make it into Drupal 6. So over the next few paragrahs I’ll lay out my grand vision for hook ordering.
Here lies James’ receipe for hook ordering in Drupal
First, the links above contain some fairly drastic ideas on how to implement a hook ordering system. I propose something a little simpler.
Reasons why, I think, my solution is simpler:
- It requires the addition of only one hook
- It requires only a few lines of extra, unobtrusive, code to the core
- It won’t affect any existing contrib or core modules
Also, please feel free to steal my ideas for something else if you like, I got the idea from the Compiz project anyway—namely their plugin dependancy system.
Addition of the hook_depends()
Sure, it sounds like reeling in a pair of adult diapers instead of trout. The API needs more humor anyway.
Ex:function mymodule_depends() { return array( 'hook_nodeapi' => array( 'before' => array( 'taxonomy_nodeapi', 'audio_nodeapi' ), 'after' => array( 'image_nodeapi' ), ), 'hook_form_alter' => array( 'before' => array( 'taxonomy_form_alter' ), ), ); }
The hook_depends is implemented by any module that cares about what order it’s hook(s) get called in. It should only be used when there is an issue with the standard hook ordering (more on that later). The idea should be quite straightforward from the example: the “mymodule” module needs its hook_nodeapi() run before a few other modules and after one, it also needs its hook_form_alter() run before one module.
How the core orders the hooks based on hook_depends()
When a new Drupal module is installed the core invokes all instances of hook_depends(). These should, in a perfect world, only exist in contrib modules because the core should take care of it’s own issues in a cleaner manner.
So, once a list of modules and their requested dependancies has been gathered the system can pour over them to figure out which order each hook should be run.
I’m sure someone else has a better algorithm than me, but here’s a shot.
- Find all implementations of the hook in question (from all modules, including ones that implement hook_depends().
- Split the list of hooks into two groups: (1) Hooks that don’t care when they are run, (2) hooks that care when they are run.
- Keep making consecutive passes over the hooks in group (2) trying to fit them into group (1) as they desire to be placed.
- If a cyclic relationship occurs then throw a nasty error (more on this later).
Once the ordered lists have been made for each hook cache the list to the database. I’m unsure if this should be done using cache_set or by keeping track of it in a seperate table entirely. Either way the result is the same.
Modifications to module_implements
Since Drupal is quite good at keeping it’s core code squeaky clean, adding hook ordering is a simple matter. Just modify module_implements() to grab the cached list of ordered hooks and everything should cascade from there.
What to do if there is a collision or cycle?
Nothing. Well throw an error, I guess. The real point is that while cyclic dependancies are easy to create it’s up to contrib module developers to avoid them. As long as the hook_depends() is used sparingly and only when there is a bonified reason cyclic dependancies shouldn’t happen.
Reasons to use hook_depends(), aka common reasons hook ordering is needed:
- Some module up the food-chain from my module is messing up my data, and I want it to stop.
- I want to mess with the data being supplied to some module down the food-chain.
Why not have ‘after all’ or ‘before all’ ordering classes?
Because somebody has to go first, and two hooks can’t both be first. It’s better to encourage developers to place their hooks directly before the module they intend to superceed. Also, wasn’t this what hook_init() an hook_exit() were created for?
All thoughts and feedback are welcome!
James.
Comments
I learned how to write “the PHP” in just one night. Here check it out…
P… H… P
Booya!