Drupal 7 - Controlling/Changing order of execution of particular hooks for modules

Drupal 7 brings some important API changes and enhancements to the table, one of them being the ability to control precisely the order of execution of a particular hook between modules.

I was asked by a client last week if it is possible to control execution of specific hooks between modules. Here is the (very precisely) framed question:

Can I ask if it is possible in Drupal to affect the order in which hooks are fired beyond the module weight system?
What if I have hook x and hook y and module 1 and 2. I want hook x to run in module 1 and then hook x to run in module 2 BUT i want hook y to run in module 2, then hook y in module 1.
 
I only seem to be able to influence hook order at module level so that hook x and hook y both run in module 1 before hook x and hook y in module 2!

And my answer was yupp, its possible, because fortunately we chose Drupal 7 for our implementation (we started on this site around mid January this year, and D7 was just launched. To us, it was a bold decision to chose D7 at that time with very limited contributed modules having a D7 version, but the choice has paid off really well with all the new abilities that D7 brings to the table).

As a Drupal developer, you would already be aware that Drupal assigns a weight to each module (that gets stored in system table). All hooks on the modules are by default invoked in order of module weights and in fact, this is the only way to control "hooking" order for modules in D6. Which means that for a module with lower weight, all hooks would be invoked before than the corresponding hooks on a module that has a higher weight. You cannot control the order of "hooking" on a per-hook basis in D6.

But D7 brings with itself this wonderful new capability of controlling "hooking" order on a per-hook per-module basis with the new hook called: hook_module_implements_alter.

Basically with this new hook, you can re-order the module hooking order for a specific hook. Here's an example of implementing this hook:

 

{syntaxhighlighter brush: php;fontsize: 100; first-line: 1; }function mymodule_module_implements_alter(&$implementations, $hook) { //Any change here requires Caches to be cleared. switch ($hook) { case 'form_alter': $m2 = $implementations['m2']; unset($implementations['m2']); $m3 = $implementations['m3']; unset($implementations['m3']); //hook_form_alter would be called on module m3 before module m2. $implementations['m3'] = $m3; $implementations['m2'] = $m2; break; case 'menu': $m2 = $implementations['m2']; unset($implementations['m2']); $m3 = $implementations['m3']; unset($implementations['m3']); //hook_menu would be called on module m2 before module m3. $implementations['m2'] = $m2; $implementations['m3'] = $m3; break; } } {/syntaxhighlighter}

The code is pretty self-explanatory I believe. The fact that Drupal iterates through $implementations with a foreach loop which PHP iterates in the order that the items were added ensures that hooks added later are invoked later. So, controlling the order of hooks just requires us to re-order the $implementations array in the desired sequence.

I would like to insist that this ability is available in only Drupal 7 and later versions. In Drupal 6, module weights control the order of hook execution which means you cannot control it on a per-hook basis between various modules.

The attached code below demonstrates this new ability in Drupal 7. It contains 3 modules. Module 1 dictates the order in which hook_form_alter is invoked on Module 2 and Module 3 by implementing hook_module_implements_alter.

While creating this test code for the purpose of this blog entry, I found a bug in Drupal's core, that I have reported here. At the time I wrote this blog entry, Drupal does not honor the changes made by hook_module_implements_alter for hook_form_form_id_alter. This is a major bug in my opinion because there would be instances where you want hook_form_form_id_alter to be invoked in different order on different forms.

A solution is to implement hook_form_alter instead whose order changes with hook_module_implements_alter are honored by Drupal. But this would mean that you would not be able to control the order of altering forms on a per form basis. I hope the Drupal team fixes this issue soon. Currently, if you want to do so, your best bet would be not to implement hook_form_form_id_alter in modules where you want to change the order. But implement hook_form_alter in another module which checks $form_id and manually invokes hook_form_form_id_alter on your modules where you want to control the order of altering forms.

 

AttachmentSize
Package icon hook_module_implements_alter.zip2.34 KB

Comments

Thanks for this, you save my life! :)

Hello, Rahul. Thank you for posting this article. I'm trying to use these ideas to cause Drupal to call a hook function for custom module, instead of the same hook function for a contrib module. To get started, I tried:

function mymodule_module_implements_alter( &$implementations, $hook ) {

    drupal_set_message( '$hook = ' . $hook . ', $implementations = ' . print_r( $implementations, true ) );

}

I expected to get an array listing all of the module names, but instead the only output is:

$hook = field_display_alter, $implementations = Array ( [node] => [token] => )

$hook = query_alter, $implementations = Array ( [node] => )

Any ideas what might be going wrong? Thank you!

rahul's picture

Well that is strange Michael, can you try clearing your site cache and then see if it helps. Also ,can you please try outputting directly to page instead of using drupal_set_message. A better option would be to append the output of print_r to a text file, then go and clear your site caches and visit some pages on your site in succession, and finally check the contents of that text file.

Thanks for your reply. Sorry for the delay in my reply -- been swamped with client work. When I was testing with my previous sample code, I flushed the caches several times, and it made no difference.

So this time, I stripped it down to the simplest sample code possible: a fresh install of Drupal 7, and a practically empty custom module:

function example_module_implements_alter( &$implementations, $hook ) {
drupal_set_message( '$hook = ' . $hook . ', $implementations = ' . print_r( $implementations, true ) );
}

Amazingly, without any cache refresh, it generated 94 lines of debug statements! So I whittled it down, just so the results weren't so overwhelming:

function example_module_implements_alter( &$implementations, $hook ) {
if ( $hook == 'theme' ) {
drupal_set_message( '$hook = ' . $hook . ', $implementations = ' . print_r( $implementations, true ) );
}
}

And now it displays:

$hook = theme, $implementations = Array ( [block] => [webform] => [addanother] => [addthis] => [captcha] => [ctools] => [date] => [date_all_day] => [date_api] => [date_popup] => [date_repeat_field] => [date_views] => [dblog] => [entity] => [field] => [field_ui] => [file] => [filter] => [image] => [menu] => [module_filter] => [nice_menus] => [node] => [options] => [page_title] => [r4032login] => [rdf] => [registration] => [search] => [system] => [taxonomy] => [token] => [update] => [user] => [views_ui] => [date_repeat] => [views] => [devel] => [admin_menu] => )

It appears to be working, although I can't be completely sure, as I've never before looked at any of this part of Drupal.

I have no idea what was going wrong before, but perhaps there was some sort of anomaly in the code. Anyway, thanks for your reply!

rahul's picture

Thanks for the update Michael, I am glad it works for you now. Please feel free to get back if the issue arises again.

Hello !

In the same logic, is it possible to know / affect order of modules installation ?? example : 

I have two custom "content-type" modules (customA and customB, without dependencies) if user try to enable twice modules at same time, I want the customA must be installed before the customB. But I don't want to use dependencies to do it, because the two modules can work alone. But if customA is installed, customB install some additional fields linked to customA installed fields.

Thanks for any suggestion Wink

rahul's picture

Hi Thierry, does hook_modules_installed seems like what you need?

Here's an idea based on this hook and hook_install (its a 2 step process):

  1. Implement hook_install in customB, use module_exists to check if customA is available and if yes, install the additional fields linked to customA.
  2. Then, implement hook_modules_installed again in customB. Iterate throught the only parameter to see if customA was installed. And if yes, install the additional fields linked to customA.

This would ensure that irrespective of the order in which the 2 modules are installed, customB would provide the additonal fields when customA is installed.

Please note you might also need to check enabled status of customA in customB as customA can be disabled after install. In that case, it might be useful for you to also implement hook_modules_enabled and enable/disable your custom fields as customA gets enabled/disabled.

Thanks for your answer, it's really interesting to see possibilities drupal7 offer :-) I'll try it as soon as possible.