Change record status: 
Project: 
Introduced in branch: 
9.4.x
Introduced in version: 
9.4.0
Description: 

All hook invocations have been been delegated as a responsibility of Module Handler service. Modules must no longer construct hook functions on their own. This will allow future improvement to the hook system since hooks/alters are fully centralized.

\Drupal\Core\Extension\ModuleHandlerInterface::getImplementations() and \Drupal\Core\Extension\ModuleHandlerInterface::implementsHook() has been deprecated. If you needed this for custom hook invocations, use Drupal\Core\Extension\ModuleHandlerInterface::invoke*With() methods instead.

The interface for module handler service has changed. \Drupal\Core\Extension\ModuleHandlerInterface introduces:

  • public function invokeAllWith(string $hook, callable $callback): void
  • public function hasImplementations(string $hook, $modules = NULL): bool

The hook invoker will no longer have any insight into the implementation of a hook, such as the function name, but will know the module name where it is defined. Thusly callers must not rely on the fact an implementation will be called, or that there will only be one implementation per module.

Responsibility for iteration has been moved from invokers to the module handler service. Invokers are now given the opportunity to pass a closure which will be invoked for each hook implementation. Each closure is passed a callable and the module name. The callable must be called to trigger the hook implementation.

All existing behavior can be replicated by migrating to \Drupal\Core\Extension\ModuleHandlerInterface::invoke*With() methods with minor changes to logic.

Examples

Old implementation

$hook = 'myhook';
foreach ($this->moduleHandler->getImplementations($hook) as $module) {
  $this->moduleHandler->invoke($module, $hook);
  // Or..
  $function = $module . '_' . $hook;
  $function(...$args);
}

New implementation

  • Change module handler method from getImplementations to invokeAllWith.
  • Remove foreach loops, instead surrounding existing logic with a closure.
  • Import dependent data with use.
  • Invoke hooks with $hook(). You may pass existing arguments to it as normal, return values also work the same.
$hook = 'myhook';

// Simple:
$this->moduleHandler->invokeAllWith($hook, function (callable $hook, string $module) {
  $hook();
});

// Advanced, if you need to import data or juggle state:
$results = [];
$this->moduleHandler->invokeAllWith($hook, function (callable $hook, string $module) use (&$results) {
  $results[$module] = $hook();
});

// Getting a list of hook implementors is as simple as making use of the $module variable.
$implementors = [];
$this->moduleHandler->invokeAllWith($hook, function (callable $hook, string $module) use (&$implementors) {
  // There is minimal overhead since the hook is not invoked.
  $implementors[] = $module;
});

Some or all invocations of the following hooks in core have been switched to use invokeAllWith

  • hook_cron
  • hook_entity_base_field_info
  • hook_entity_bundle_field_info
  • hook_entity_field_access
  • hook_entity_field_access
  • hook_entity_field_storage_info
  • hook_entity_load
  • hook_ENTITY_TYPE_load
  • hook_entity_storage_load
  • hook_ENTITY_TYPE_storage_load
  • hook_entity_type_build
  • hook_field_formatter_third_party_settings_form
  • hook_field_widget_third_party_settings_form
  • hook_help
  • hook_js_settings_build
  • hook_jsonapi_ENTITY_TYPE_filter_access
  • hook_jsonapi_entity_filter_access
  • hook_mail
  • hook_node_grants
  • hook_page_attachments
  • hook_page_bottom
  • hook_page_top
  • hook_rdf_namespaces
  • hook_search_preprocess
  • hook_update_last_removed
  • hook_views_data
Impacts: 
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done