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
toinvokeAllWith
. - 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