Changing the Order of Hooks in Drupal

Changing the Order of Hooks in Drupal image

The module and hook systems are as old as Drupal. From the documentation in Drupal 1.0:

In places where hooks are made available, the engine calls each module's exported functions. This is done by iterating through the modules directory where all modules must reside. Say your module is named foo (i.e. modules/foo.module) and if there was a hook called bar, the engine will call foo_bar() if this was exported by your module.

Although the Content Management System (CMS) has undergone a lot of changes since those early days, modules and hooks still play a great role even in Drupal 8.

To gather information about available modules, Drupal scans certain directories and builds an array of data keyed and sorted by the module filename without the extension which is the machine name of the module. Then the records in the system table are updated based on the array of file data. That is what happens in the system_rebuild_module_data() function in Drupal 7. The architecture in Drupal 8 is different so, this is mainly managed by the ModuleHandler and Extension classes.

When going through certain processes on a set of modules, Drupal orders them by the weight column in the system table and filename. The lower integers come first — lighter values float to the top of the list. For instance, where there are multiple implementations of the same hook, the modules are ordered by weight and filename and the functions executed accordingly.

A problem arises when your requirements make it necessary either to run your hook implementation first or last. For example, in a hook_form_FORM_ID_alter you want to have the final say in the ordering of the elements.

Drupal 7

There are two options:

db_query()

Prior to Drupal 7, in order to run a module's hook implementations first or last one may write an SQL query to update the weight column of the system table.

This database update will change the position of your custom_hooks_demo module in the array of modules and make sure the hooks in the module get fired first:

// Drupal 6/7
db_query("UPDATE {system} SET weight = -10000 WHERE name = 'custom_hooks_demo'");

Conversely, to run them last, you set the weight ridiculously high:

  // Drupal 6/7
  db_query("UPDATE {system} SET weight = 10000 WHERE name = 'custom_hooks_demo'");

Well and good. However, your module is likely to have more than one hook implementation, and you may want to run them differently.

hook_module_implements_alter()

To resolve the problem of re-ordering hook implementations, the hook_module_implements_alter() was introduced in Drupal 7.

Let us consider Views module results. Assuming we want to display the results randomly and we have implemented hook_views_pre_render. It makes sense to run our code last.

In this implementation of hook_module_implements_alter we do this by copying our implementation and removing it from its original position in the array of all implementations. Then we append the copy to the end of the array.

  function custom_hooks_demo_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'views_pre_render') {
    $group = $implementations['custom_hooks_demo'];
    unset($implementations['custom_hooks_demo']);
    $implementations['custom_hooks_demo'] = $group;
  }
}

Using the same example of hook_views_pre_render, we might want to modify the records by inserting or removing a column before other pre_render hooks; this is how to do it. The copying and deletion are done as before, but we prepend our implementation to the array.

  function custom_hooks_demo_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'views_pre_render') {
    $group = $implementations['custom_hooks_demo'];
    unset($implementations['custom_hooks_demo']);
    $implementations = $group + $implementations;
  }
}

Drupal 8

What happens in Drupal 8? The good news is that hook_module_implements_alter still retains the same syntax and behavior. Here is a snippet from core:

  // core/modules/content_translation/content_translation.module

  /** * Implements hook_module_implements_alter(). */
  function content_translation_module_implements_alter(&$implementations, $hook) {
  switch ($hook) {
    // Move our hook_entity_type_alter() implementation to the end of the list.
    case 'entity_type_alter':
      $group = $implementations['content_translation'];
      unset($implementations['content_translation']);
      $implementations['content_translation'] = $group;
      break;
  }
}

In addition, there is an API function to set the weight of a module:

module_set_weight('my_module', 123);

See the implementation in core\includes\module.inc.

Conclusion

A lot has changed in Drupal 8, but a lot has not changed either. As long as the hook system remains part of the CMS, module_implements_alter will always be useful. And the best thing is that there is nothing new to learn here.

Code

The demo module for this article is in the X-Team Drupal 8 Examples Github repository.

KEEP MOVING FORWARD

Deji Akala / code