0

I have 2 custom modules in Drupal 9, let's called them module A and module B.

Each module has a fully working hook_preprocess_html function defined in the .module file. The hook_preprocess_html function in module A runs first, followed by the function in module B.

How can I update Module A so the hook_preprocess_html function in there runs after the one in module B?

I have tried to implement hook_module_implements_alter(&$implementations, $hook) but it is not even showing the hook_preprocess_html hook. This preprocess_html seems not to apply to the module_implements_alter hook.

Many thanks for you help!

1

2 Answers 2

0

hook_module_implements_alter() is invoked only for those hooks that are invoked via ModuleHandler::alter(), ModuleHandler::invokeAll(), and ModuleHandler::invokeAllWith(). All those methods call ModuleHandler::getImplementationInfo(), which calls ModuleHandler::buildImplementationInfo(), which contains the following code. (See the line after the Verify implementations that were added or modified. comment.)

  // Allow modules to change the weight of specific implementations, but avoid
  // an infinite loop.
  if ($hook != 'module_implements_alter') {
    // Remember the original implementations, before they are modified with
    // hook_module_implements_alter().
    $implementations_before = $implementations;

    // Verify implementations that were added or modified.
    $this->alter('module_implements', $implementations, $hook);

    // Verify new or modified implementations.
    foreach (array_diff_assoc($implementations, $implementations_before) as $module => $group) {
      // If an implementation of hook_module_implements_alter() changed or
      // added a group, the respective file needs to be included.
      if ($group) {
        $this->loadInclude($module, 'inc', "{$module}.{$group}");
      }

      // If a new implementation was added, verify that the function exists.
      if (!function_exists($module . '_' . $hook)) {
        throw new \RuntimeException("An invalid implementation {$module}_{$hook} was added by hook_module_implements_alter()");
      }
    }
  }

Registry::processExtension() creates the list of preprocess hooks implemented for a single theme hook with the following code.

foreach ($prefixes as $prefix) {
  if (isset($info['template']) && function_exists($prefix . '_preprocess')) {
    $info['preprocess functions'][] = $prefix . '_preprocess';
  }
  if (function_exists($prefix . '_preprocess_' . $hook)) {
    $info['preprocess functions'][] = $prefix . '_preprocess_' . $hook;
  }
}

$prefixes is initialized from the following code. In particular, see the if ($type == 'module') { /* … */ } part, which is the one that allows to modules to add their own variable preprocessors (a.k.a. preprocess hooks).

(I left only the relevant comment to make the code shorter. All the comments can be read in the linked documentation page, which shows also the method code.)

if ($type == 'module') {
  $prefixes[] = 'template';

  // Add all modules so they can intervene with their own variable
  // preprocessors. This allows them to provide variable preprocessors
  // even if they are not the owner of the current hook.
  $prefixes = array_merge($prefixes, $module_list);
}
elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
  $prefixes[] = $name . '_engine';
  $prefixes[] = $theme;
}
else {
  $prefixes[] = $name;
}

$module_list is initialized with $module_list = array_keys($this->moduleHandler->getModuleList());

The code that invokes the preprocess hooks does not use the methods from the ModuleHandler class I listed earlier, since it builds the list of preprocess hooks to invoke using Registry::processExtension(), which means hook_module_implements_alter() is not invoked for preprocess hooks.

The list of modules returned by ModuleHandler::getModuleList() is ordered by the modules weight, though. This means that changing a module weight will change the order preprocess hooks are invoked.

6
  • Thanks, that makes sense. Does this mead that they only way to affect the order of preprocessors is to set a module weight at install? Commented Jul 28, 2023 at 9:53
  • If the list of modules is taken from container.modules, or from a function/method that returns the value of that container parameter, yes. That includes the code that calls ModuleHandler::getModuleList(), which is called from the method that uses the last code lines I show in the answer.
    – apaderno
    Commented Jul 28, 2023 at 10:34
  • I am going to expand the answer to show that the method that creates the list of preprocess hooks uses ModuleHandler::getModuleList(), which would make more evident the code is not using ModuleHandler::invokeAll(), for example.
    – apaderno
    Commented Jul 28, 2023 at 10:36
  • Done: Now the answer should be clearer about changing a module weight to change the order the preprocess hooks are invoked.
    – apaderno
    Commented Jul 28, 2023 at 11:05
  • Excellent, thank you. I changed the module weight and now I'm getting things in teh right order for me. I am surprised there's no other way, as it's not always possible to change a module weight by uninstalling/ re)installing once live. Commented Jul 31, 2023 at 14:51
0

Is there a hook_preprocess_hook in the list? As the doc says hook_module_implements_alter will only show the base hooks and hook_preprocess_html is a variant of hook_preprocess_hook

So you would need to change the weight for hook_preprocess_hook

1
  • Thanks for your reply. No, hook_preprocess_hook is not in the list, which I guess is explained by apaderno. Commented Jul 28, 2023 at 9:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.