diff --git a/core/lib/Drupal/Core/Asset/AssetResolver.php b/core/lib/Drupal/Core/Asset/AssetResolver.php index 35d28bbe8ce..7f4b110bc52 100644 --- a/core/lib/Drupal/Core/Asset/AssetResolver.php +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php @@ -312,10 +312,9 @@ class AssetResolver implements AssetResolverInterface { if ($settings_required && $settings_have_changed) { $settings = $this->getJsSettingsAssets($assets); // Allow modules to add cached JavaScript settings. - foreach ($this->moduleHandler->getImplementations('js_settings_build') as $module) { - $function = $module . '_js_settings_build'; - $function($settings, $assets); - } + $this->moduleHandler->invokeAllWith('js_settings_build', function (callable $hook, string $module) use (&$settings, $assets) { + $hook($settings, $assets); + }); } $settings_in_header = in_array('core/drupalSettings', $header_js_libraries); $this->cache->set($cid, [$js_assets_header, $js_assets_footer, $settings, $settings_in_header], CacheBackendInterface::CACHE_PERMANENT, ['library_info']); diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php index b863112c2af..702441d1fbe 100644 --- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php +++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php @@ -358,7 +358,7 @@ class LibraryDiscoveryParser { // Allow modules to add dynamic library definitions. $hook = 'library_info_build'; - if ($this->moduleHandler->implementsHook($extension, $hook)) { + if ($this->moduleHandler->hasImplementations($hook, $extension)) { $libraries = NestedArray::mergeDeep($libraries, $this->moduleHandler->invoke($extension, $hook)); } diff --git a/core/lib/Drupal/Core/Cron.php b/core/lib/Drupal/Core/Cron.php index c45dfbd2876..5464f7238d0 100644 --- a/core/lib/Drupal/Core/Cron.php +++ b/core/lib/Drupal/Core/Cron.php @@ -229,8 +229,7 @@ class Cron implements CronInterface { $logger = $time_logging_enabled ? $this->logger : new NullLogger(); // Iterate through the modules calling their cron handlers (if any): - foreach ($this->moduleHandler->getImplementations('cron') as $module) { - + $this->moduleHandler->invokeAllWith('cron', function (callable $hook, string $module) use (&$module_previous, $logger) { if (!$module_previous) { $logger->info('Starting execution of @module_cron().', [ '@module' => $module, @@ -247,7 +246,7 @@ class Cron implements CronInterface { // Do not let an exception thrown by one module disturb another. try { - $this->moduleHandler->invoke($module, 'cron'); + $hook(); } catch (\Exception $e) { watchdog_exception('cron', $e); @@ -255,7 +254,7 @@ class Cron implements CronInterface { Timer::stop('cron_' . $module); $module_previous = $module; - } + }); if ($module_previous) { $logger->info('Execution of @module_previous_cron() took @time.', [ '@module_previous' => $module_previous, diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index b5cb8ce0fbf..8d02da64f17 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -862,15 +862,19 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Con protected function invokeStorageLoadHook(array &$entities) { if (!empty($entities)) { // Call hook_entity_storage_load(). - foreach ($this->moduleHandler()->getImplementations('entity_storage_load') as $module) { - $function = $module . '_entity_storage_load'; - $function($entities, $this->entityTypeId); - } + $this->moduleHandler()->invokeAllWith( + 'entity_storage_load', + function (callable $hook, string $module) use (&$entities) { + $hook($entities, $this->entityTypeId); + } + ); // Call hook_TYPE_storage_load(). - foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_storage_load') as $module) { - $function = $module . '_' . $this->entityTypeId . '_storage_load'; - $function($entities); - } + $this->moduleHandler()->invokeAllWith( + $this->entityTypeId . '_storage_load', + function (callable $hook, string $module) use (&$entities) { + $hook($entities); + } + ); } } diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php index b6b39c38226..25c3141105d 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php @@ -342,10 +342,12 @@ class EntityAccessControlHandler extends EntityHandlerBase implements EntityAcce // Invoke hook and collect grants/denies for field access from other // modules. $grants = []; - $hook_implementations = $this->moduleHandler()->getImplementations('entity_field_access'); - foreach ($hook_implementations as $module) { - $grants[] = [$module => $this->moduleHandler()->invoke($module, 'entity_field_access', [$operation, $field_definition, $account, $items])]; - } + $this->moduleHandler()->invokeAllWith( + 'entity_field_access', + function (callable $hook, string $module) use ($operation, $field_definition, $account, $items, &$grants) { + $grants[] = [$module => $hook($operation, $field_definition, $account, $items)]; + } + ); // Our default access flag is masked under the ':default' key. $grants = array_merge([':default' => $default], ...$grants); diff --git a/core/lib/Drupal/Core/Entity/EntityFieldManager.php b/core/lib/Drupal/Core/Entity/EntityFieldManager.php index 4b1b9b1d7fe..e38d823a9a2 100644 --- a/core/lib/Drupal/Core/Entity/EntityFieldManager.php +++ b/core/lib/Drupal/Core/Entity/EntityFieldManager.php @@ -282,9 +282,10 @@ class EntityFieldManager implements EntityFieldManagerInterface { } // Retrieve base field definitions from modules. - foreach ($this->moduleHandler->getImplementations('entity_base_field_info') as $module) { - $module_definitions = $this->moduleHandler->invoke($module, 'entity_base_field_info', [$entity_type]); - if (!empty($module_definitions)) { + $this->moduleHandler->invokeAllWith( + 'entity_base_field_info', + function (callable $hook, string $module) use (&$base_field_definitions, $entity_type) { + $module_definitions = $hook($entity_type) ?? []; // Ensure the provider key actually matches the name of the provider // defining the field. foreach ($module_definitions as $field_name => $definition) { @@ -296,7 +297,7 @@ class EntityFieldManager implements EntityFieldManagerInterface { $base_field_definitions[$field_name] = $definition; } } - } + ); // Automatically set the field name, target entity type and bundle // for non-configurable fields. @@ -404,9 +405,10 @@ class EntityFieldManager implements EntityFieldManagerInterface { } // Retrieve base field definitions from modules. - foreach ($this->moduleHandler->getImplementations('entity_bundle_field_info') as $module) { - $module_definitions = $this->moduleHandler->invoke($module, 'entity_bundle_field_info', [$entity_type, $bundle, $base_field_definitions]); - if (!empty($module_definitions)) { + $this->moduleHandler->invokeAllWith( + 'entity_bundle_field_info', + function (callable $hook, string $module) use (&$bundle_field_definitions, $entity_type, $bundle, $base_field_definitions) { + $module_definitions = $hook($entity_type, $bundle, $base_field_definitions) ?? []; // Ensure the provider key actually matches the name of the provider // defining the field. foreach ($module_definitions as $field_name => $definition) { @@ -418,7 +420,7 @@ class EntityFieldManager implements EntityFieldManagerInterface { $bundle_field_definitions[$field_name] = $definition; } } - } + ); // Automatically set the field name, target entity type and bundle // for non-configurable fields. @@ -580,9 +582,10 @@ class EntityFieldManager implements EntityFieldManagerInterface { $field_definitions = []; // Retrieve base field definitions from modules. - foreach ($this->moduleHandler->getImplementations('entity_field_storage_info') as $module) { - $module_definitions = $this->moduleHandler->invoke($module, 'entity_field_storage_info', [$entity_type]); - if (!empty($module_definitions)) { + $this->moduleHandler->invokeAllWith( + 'entity_field_storage_info', + function (callable $hook, string $module) use (&$field_definitions, $entity_type, $entity_type_id) { + $module_definitions = $hook($entity_type) ?? []; // Ensure the provider key actually matches the name of the provider // defining the field. foreach ($module_definitions as $field_name => $definition) { @@ -596,7 +599,7 @@ class EntityFieldManager implements EntityFieldManagerInterface { $field_definitions[$field_name] = $definition; } } - } + ); // Invoke alter hook. $this->moduleHandler->alter('entity_field_storage_info', $field_definitions, $entity_type); diff --git a/core/lib/Drupal/Core/Entity/EntityForm.php b/core/lib/Drupal/Core/Entity/EntityForm.php index a29c392de90..3ae6bd04272 100644 --- a/core/lib/Drupal/Core/Entity/EntityForm.php +++ b/core/lib/Drupal/Core/Entity/EntityForm.php @@ -387,16 +387,11 @@ class EntityForm extends FormBase implements EntityFormInterface { * The current state of the form. */ protected function prepareInvokeAll($hook, FormStateInterface $form_state) { - $implementations = $this->moduleHandler->getImplementations($hook); - foreach ($implementations as $module) { - $function = $module . '_' . $hook; - if (function_exists($function)) { - // Ensure we pass an updated translation object and form display at - // each invocation, since they depend on form state which is alterable. - $args = [$this->entity, $this->operation, &$form_state]; - call_user_func_array($function, $args); - } - } + $this->moduleHandler->invokeAllWith($hook, function (callable $hook, string $module) use ($form_state) { + // Ensure we pass an updated translation object and form display at + // each invocation, since they depend on form state which is alterable. + $hook($this->entity, $this->operation, $form_state); + }); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php index 2459819f534..d32d8fbca68 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php @@ -393,16 +393,12 @@ abstract class EntityStorageBase extends EntityHandlerBase implements EntityStor $entity_class::postLoad($this, $items); } } - // Call hook_entity_load(). - foreach ($this->moduleHandler()->getImplementations('entity_load') as $module) { - $function = $module . '_entity_load'; - $function($entities, $this->entityTypeId); - } - // Call hook_TYPE_load(). - foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_load') as $module) { - $function = $module . '_' . $this->entityTypeId . '_load'; - $function($entities); - } + $this->moduleHandler()->invokeAllWith('entity_load', function (callable $hook, string $module) use (&$entities) { + $hook($entities, $this->entityTypeId); + }); + $this->moduleHandler()->invokeAllWith($this->entityTypeId . '_load', function (callable $hook, string $module) use (&$entities) { + $hook($entities); + }); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityTypeManager.php b/core/lib/Drupal/Core/Entity/EntityTypeManager.php index 8b7daa354fe..3ddea3eb0c6 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeManager.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeManager.php @@ -114,13 +114,9 @@ class EntityTypeManager extends DefaultPluginManager implements EntityTypeManage */ protected function findDefinitions() { $definitions = $this->getDiscovery()->getDefinitions(); - - // Directly call the hook implementations to pass the definitions to them - // by reference, so new entity types can be added. - foreach ($this->moduleHandler->getImplementations('entity_type_build') as $module) { - $function = $module . '_entity_type_build'; - $function($definitions); - } + $this->moduleHandler->invokeAllWith('entity_type_build', function (callable $hook, string $module) use (&$definitions) { + $hook($definitions); + }); foreach ($definitions as $plugin_id => $definition) { $this->processDefinition($definition, $plugin_id); } diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 6a84fa576be..29c9da5301c 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -325,6 +325,7 @@ class ModuleHandler implements ModuleHandlerInterface { * {@inheritdoc} */ public function getImplementations($hook) { + @trigger_error('ModuleHandlerInterface::getImplementations() is deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. Instead you should use ModuleHandlerInterface::invokeAllWith() for hook invocations, or you should use ModuleHandlerInterface::hasImplementations() to determine if hooks implementations exist. See https://www.drupal.org/node/3000490', E_USER_DEPRECATED); $implementations = $this->getImplementationInfo($hook); return array_keys($implementations); } @@ -355,8 +356,8 @@ class ModuleHandler implements ModuleHandlerInterface { // when non-database caching backends are used, so there will be more // significant gains when a large number of modules are installed or hooks // invoked, since this can quickly lead to - // \Drupal::moduleHandler()->implementsHook() being called several thousand - // times per request. + // \Drupal::moduleHandler()->hasImplementations() being called several + // thousand times per request. $this->cacheBackend->set('module_implements', []); $this->cacheBackend->delete('hook_info'); } @@ -364,32 +365,56 @@ class ModuleHandler implements ModuleHandlerInterface { /** * {@inheritdoc} */ - public function implementsHook($module, $hook) { - $function = $module . '_' . $hook; - if (function_exists($function)) { - return TRUE; - } - // If the hook implementation does not exist, check whether it lives in an - // optional include file registered via hook_hook_info(). - $hook_info = $this->getHookInfo(); - if (isset($hook_info[$hook]['group'])) { - $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); - if (function_exists($function)) { - return TRUE; + public function hasImplementations(string $hook, $modules = NULL): bool { + if ($modules !== NULL) { + foreach ((array) $modules as $module) { + // Hook implementations usually found in a module's .install file are + // not stored in the implementation info cache. In order to invoke hooks + // like hook_schema() and hook_requirements() the module's .install file + // must be included by the calling code. Additionally, this check avoids + // unnecessary work when a hook implementation is present in a module's + // .module file. + if (function_exists($module . '_' . $hook)) { + return TRUE; + } } } - return FALSE; + + $implementations = $this->getImplementationInfo($hook); + if ($modules === NULL && !empty($implementations)) { + return TRUE; + } + + return !empty(array_intersect((array) $modules, array_keys($implementations))); + } + + /** + * {@inheritdoc} + */ + public function implementsHook($module, $hook) { + @trigger_error('ModuleHandlerInterface::implementsHook() is deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. Instead you should use ModuleHandlerInterface::hasImplementations() with the $modules argument. See https://www.drupal.org/node/3000490', E_USER_DEPRECATED); + return $this->hasImplementations($hook, $module); + } + + /** + * {@inheritdoc} + */ + public function invokeAllWith(string $hook, callable $callback): void { + foreach (array_keys($this->getImplementationInfo($hook)) as $module) { + $hookInvoker = \Closure::fromCallable($module . '_' . $hook); + $callback($hookInvoker, $module); + } } /** * {@inheritdoc} */ public function invoke($module, $hook, array $args = []) { - if (!$this->implementsHook($module, $hook)) { + if (!$this->hasImplementations($hook, $module)) { return; } - $function = $module . '_' . $hook; - return call_user_func_array($function, $args); + $hookInvoker = \Closure::fromCallable($module . '_' . $hook); + return call_user_func_array($hookInvoker, $args); } /** @@ -397,18 +422,15 @@ class ModuleHandler implements ModuleHandlerInterface { */ public function invokeAll($hook, array $args = []) { $return = []; - $implementations = $this->getImplementations($hook); - foreach ($implementations as $module) { - $function = $module . '_' . $hook; - $result = call_user_func_array($function, $args); + $this->invokeAllWith($hook, function (callable $hook, string $module) use ($args, &$return) { + $result = call_user_func_array($hook, $args); if (isset($result) && is_array($result)) { $return = NestedArray::mergeDeep($return, $result); } elseif (isset($result)) { $return[] = $result; } - } - + }); return $return; } @@ -478,10 +500,10 @@ class ModuleHandler implements ModuleHandlerInterface { if (!isset($this->alterFunctions[$cid])) { $this->alterFunctions[$cid] = []; $hook = $type . '_alter'; - $modules = $this->getImplementations($hook); + $modules = array_keys($this->getImplementationInfo($hook)); if (!isset($extra_types)) { // For the more common case of a single hook, we do not need to call - // function_exists(), since $this->getImplementations() returns only + // function_exists(), since $this->getImplementationInfo() returns only // modules with implementations. foreach ($modules as $module) { $this->alterFunctions[$cid][] = $module . '_' . $hook; @@ -492,21 +514,21 @@ class ModuleHandler implements ModuleHandlerInterface { // implements at least one of them. $extra_modules = []; foreach ($extra_types as $extra_type) { - $extra_modules[] = $this->getImplementations($extra_type . '_alter'); + $extra_modules[] = array_keys($this->getImplementationInfo($extra_type . '_alter')); } $extra_modules = array_merge([], ...$extra_modules); // If any modules implement one of the extra hooks that do not implement // the primary hook, we need to add them to the $modules array in their - // appropriate order. $this->getImplementations() can only return + // appropriate order. $this->getImplementationInfo() can only return // ordered implementations of a single hook. To get the ordered // implementations of multiple hooks, we mimic the - // $this->getImplementations() logic of first ordering by + // $this->getImplementationInfo() logic of first ordering by // $this->getModuleList(), and then calling // $this->alter('module_implements'). if (array_diff($extra_modules, $modules)) { // Merge the arrays and order by getModuleList(). $modules = array_intersect(array_keys($this->moduleList), array_merge($modules, $extra_modules)); - // Since $this->getImplementations() already took care of loading the + // Since $this->getImplementationInfo() already took care of loading the // necessary include files, we can safely pass FALSE for the array // values. $implementations = array_fill_keys($modules, FALSE); diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php index 0258ac0953c..909ce63d449 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php @@ -180,6 +180,13 @@ interface ModuleHandlerInterface { * * @return array * An array with the names of the modules which are implementing this hook. + * + * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. Instead you + * should use ModuleHandlerInterface::invokeAllWith() for hook invocations + * or you should use ModuleHandlerInterface::hasImplementations() to + * determine if hooks implementations exist. + * + * @see https://www.drupal.org/node/3000490 */ public function getImplementations($hook); @@ -193,6 +200,23 @@ interface ModuleHandlerInterface { */ public function resetImplementations(); + /** + * Determines whether there are implementations of a hook. + * + * @param string $hook + * The name of the hook (e.g. "help" or "menu"). + * @param string|string[]|null $modules + * (optional) A single module or multiple modules to check if they have any + * implementations of a hook. Use NULL to check if any enabled module has + * implementations. + * + * @return bool + * If $modules is provided, then TRUE if there are any implementations by + * the module(s) provided. Or if $modules if NULL, then TRUE if there are + * any implementations. Otherwise FALSE. + */ + public function hasImplementations(string $hook, $modules = NULL): bool; + /** * Returns whether a given module implements a given hook. * @@ -204,9 +228,31 @@ interface ModuleHandlerInterface { * @return bool * TRUE if the module is both installed and enabled, and the hook is * implemented in that module. + * + * @deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. Use the + * hasImplementations() methods instead with the $modules argument. + * + * @see https://www.drupal.org/node/3000490 */ public function implementsHook($module, $hook); + /** + * Executes a callback for each implementation of a hook. + * + * The callback is passed two arguments, a closure which executes a hook + * implementation. And the module name. + * + * @param string $hook + * The name of the hook to invoke. + * @param callable $callback + * A callable that invokes a hook implementation. Such that + * $callback is callable(callable, string): mixed. + * Arguments: + * - Closure to a hook implementation. + * - Implementation module machine name. + */ + public function invokeAllWith(string $hook, callable $callback): void; + /** * Invokes a hook in a particular module. * diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php index ef52111e167..9e281bd006b 100644 --- a/core/lib/Drupal/Core/Extension/module.api.php +++ b/core/lib/Drupal/Core/Extension/module.api.php @@ -94,7 +94,7 @@ function hook_hook_info() { /** * Alter the registry of modules implementing a hook. * - * This hook is invoked during \Drupal::moduleHandler()->getImplementations(). + * This hook is invoked in \Drupal::moduleHandler()->getImplementationInfo(). * A module may implement this hook in order to reorder the implementing * modules, which are otherwise ordered by the module's system weight. * @@ -117,7 +117,7 @@ function hook_hook_info() { function hook_module_implements_alter(&$implementations, $hook) { if ($hook == 'form_alter') { // Move my_module_form_alter() to the end of the list. - // \Drupal::moduleHandler()->getImplementations() + // \Drupal::moduleHandler()->getImplementationInfo() // iterates through $implementations with a foreach loop which PHP iterates // in the order that the items were added, so to move an item to the end of // the array, we remove it and then add it. diff --git a/core/lib/Drupal/Core/Mail/MailManager.php b/core/lib/Drupal/Core/Mail/MailManager.php index 05c0c8adfa9..8ba671d50f5 100644 --- a/core/lib/Drupal/Core/Mail/MailManager.php +++ b/core/lib/Drupal/Core/Mail/MailManager.php @@ -268,12 +268,8 @@ class MailManager extends DefaultPluginManager implements MailManagerInterface { $message['headers'] = $headers; // Build the email (get subject and body, allow additional headers) by - // invoking hook_mail() on this module. We cannot use - // moduleHandler()->invoke() as we need to have $message by reference in - // hook_mail(). - if (function_exists($function = $module . '_mail')) { - $function($key, $message, $params); - } + // invoking hook_mail() on this module. + $this->moduleHandler->invoke($module, 'mail', [$key, &$message, $params]); // Invoke hook_mail_alter() to allow all modules to alter the resulting // email. diff --git a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php index 51040cd0d3f..423a7c5e066 100644 --- a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php +++ b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php @@ -46,13 +46,13 @@ class HookDiscovery implements DiscoveryInterface { */ public function getDefinitions() { $definitions = []; - foreach ($this->moduleHandler->getImplementations($this->hook) as $module) { - $result = $this->moduleHandler->invoke($module, $this->hook); - foreach ($result as $plugin_id => $definition) { + $this->moduleHandler->invokeAllWith($this->hook, function (callable $hook, string $module) use (&$definitions) { + $module_definitions = $hook(); + foreach ($module_definitions as $plugin_id => $definition) { $definition['provider'] = $module; $definitions[$plugin_id] = $definition; } - } + }); return $definitions; } diff --git a/core/lib/Drupal/Core/Plugin/Discovery/InfoHookDecorator.php b/core/lib/Drupal/Core/Plugin/Discovery/InfoHookDecorator.php index c81d30de7ea..df31a1682ea 100644 --- a/core/lib/Drupal/Core/Plugin/Discovery/InfoHookDecorator.php +++ b/core/lib/Drupal/Core/Plugin/Discovery/InfoHookDecorator.php @@ -44,10 +44,12 @@ class InfoHookDecorator implements DiscoveryInterface { */ public function getDefinitions() { $definitions = $this->decorated->getDefinitions(); - foreach (\Drupal::moduleHandler()->getImplementations($this->hook) as $module) { - $function = $module . '_' . $this->hook; - $function($definitions); - } + \Drupal::moduleHandler()->invokeAllWith( + $this->hook, + function (callable $hook, string $module) use (&$definitions) { + $hook($definitions); + } + ); return $definitions; } diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php index 09d946d22f6..460f5436602 100644 --- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php @@ -307,10 +307,12 @@ class HtmlRenderer implements MainContentRendererInterface { public function invokePageAttachmentHooks(array &$page) { // Modules can add attachments. $attachments = []; - foreach ($this->moduleHandler->getImplementations('page_attachments') as $module) { - $function = $module . '_page_attachments'; - $function($attachments); - } + $this->moduleHandler->invokeAllWith( + 'page_attachments', + function (callable $hook, string $module) use (&$attachments) { + $hook($attachments); + } + ); if (array_diff(array_keys($attachments), ['#attached', '#cache']) !== []) { throw new \LogicException('Only #attached and #cache may be set in hook_page_attachments().'); } @@ -346,14 +348,18 @@ class HtmlRenderer implements MainContentRendererInterface { // Modules can add render arrays to the top and bottom of the page. $page_top = []; $page_bottom = []; - foreach ($this->moduleHandler->getImplementations('page_top') as $module) { - $function = $module . '_page_top'; - $function($page_top); - } - foreach ($this->moduleHandler->getImplementations('page_bottom') as $module) { - $function = $module . '_page_bottom'; - $function($page_bottom); - } + $this->moduleHandler->invokeAllWith( + 'page_top', + function (callable $hook, string $module) use (&$page_top) { + $hook($page_top); + } + ); + $this->moduleHandler->invokeAllWith( + 'page_bottom', + function (callable $hook, string $module) use (&$page_bottom) { + $hook($page_bottom); + } + ); if (!empty($page_top)) { $html['page_top'] = $page_top; } diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php index 228553a5db3..9a4182919a5 100644 --- a/core/lib/Drupal/Core/Theme/Registry.php +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -348,9 +348,9 @@ class Registry implements DestructableInterface { $cache = $cached->data; } else { - foreach ($this->moduleHandler->getImplementations('theme') as $module) { + $this->moduleHandler->invokeAllWith('theme', function (callable $callback, string $module) use (&$cache) { $this->processExtension($cache, $module, 'module', $module, $this->moduleList->getPath($module)); - } + }); // Only cache this registry if all modules are loaded. if ($this->moduleHandler->isLoaded()) { $this->cache->set("theme_registry:build:modules", $cache, Cache::PERMANENT, ['theme_registry']); diff --git a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php index 893b8dd022d..2c37f34125f 100644 --- a/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php +++ b/core/modules/comment/src/Plugin/EntityReferenceSelection/CommentSelection.php @@ -86,7 +86,7 @@ class CommentSelection extends DefaultSelection { // Passing the query to node_query_node_access_alter() is sadly // insufficient for nodes. // @see \Drupal\node\Plugin\EntityReferenceSelection\NodeSelection::buildEntityQuery() - if (!$this->currentUser->hasPermission('bypass node access') && !count($this->moduleHandler->getImplementations('node_grants'))) { + if (!$this->currentUser->hasPermission('bypass node access') && !$this->moduleHandler->hasImplementations('node_grants')) { $query->condition($node_alias . '.status', 1); } } diff --git a/core/modules/dblog/tests/src/Kernel/DbLogTest.php b/core/modules/dblog/tests/src/Kernel/DbLogTest.php index ab20ff40896..4d96dbc4a00 100644 --- a/core/modules/dblog/tests/src/Kernel/DbLogTest.php +++ b/core/modules/dblog/tests/src/Kernel/DbLogTest.php @@ -44,10 +44,16 @@ class DbLogTest extends KernelTestBase { $this->assertGreaterThan($row_limit, $count, new FormattableMarkup('Dblog row count of @count exceeds row limit of @limit', ['@count' => $count, '@limit' => $row_limit])); // Get the number of enabled modules. Cron adds a log entry for each module. - $list = $this->container->get('module_handler')->getImplementations('cron'); - $module_count = count($list); + $implementation_count = 0; + \Drupal::moduleHandler()->invokeAllWith( + 'cron', + function (callable $hook, string $module) use (&$implementation_count) { + $implementation_count++; + } + ); + $cron_detailed_count = $this->runCron(); - $this->assertEquals($module_count + 2, $cron_detailed_count, new FormattableMarkup('Cron added @count of @expected new log entries', ['@count' => $cron_detailed_count, '@expected' => $module_count + 2])); + $this->assertEquals($implementation_count + 2, $cron_detailed_count, new FormattableMarkup('Cron added @count of @expected new log entries', ['@count' => $cron_detailed_count, '@expected' => $implementation_count + 2])); // Test disabling of detailed cron logging. $this->config('system.cron')->set('logging', 0)->save(); diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 42327166548..ad9b8f538b2 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -112,7 +112,7 @@ function field_help($route_name, RouteMatchInterface $route_match) { // hook_help(). if (isset($modules[$provider])) { $display = \Drupal::moduleHandler()->getName($provider); - if (\Drupal::moduleHandler()->implementsHook($provider, 'help')) { + if (\Drupal::moduleHandler()->hasImplementations('help', $provider)) { $items[] = Link::fromTextAndUrl($display, Url::fromRoute('help.page', ['name' => $provider]))->toRenderable(); } else { diff --git a/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php b/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php index fc94687a725..772c2e7e8e9 100644 --- a/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php +++ b/core/modules/field/tests/src/Unit/FieldStorageConfigAccessControlHandlerTest.php @@ -107,10 +107,6 @@ class FieldStorageConfigAccessControlHandlerTest extends UnitTestCase { ->willReturn('node'); $this->moduleHandler = $this->createMock(ModuleHandlerInterface::class); - $this->moduleHandler - ->expects($this->any()) - ->method('getImplementations') - ->will($this->returnValue([])); $this->moduleHandler ->expects($this->any()) ->method('invokeAll') diff --git a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php index 6ee46132f2d..527e5f858ae 100644 --- a/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php +++ b/core/modules/field_ui/src/Form/EntityFormDisplayEditForm.php @@ -120,15 +120,18 @@ class EntityFormDisplayEditForm extends EntityDisplayFormBase { $settings_form = []; // Invoke hook_field_widget_third_party_settings_form(), keying resulting // subforms by module name. - foreach ($this->moduleHandler->getImplementations('field_widget_third_party_settings_form') as $module) { - $settings_form[$module] = $this->moduleHandler->invoke($module, 'field_widget_third_party_settings_form', [ - $plugin, - $field_definition, - $this->entity->getMode(), - $form, - $form_state, - ]); - } + $this->moduleHandler->invokeAllWith( + 'field_widget_third_party_settings_form', + function (callable $hook, string $module) use (&$settings_form, $plugin, $field_definition, &$form, $form_state) { + $settings_form[$module] = $hook( + $plugin, + $field_definition, + $this->entity->getMode(), + $form, + $form_state + ); + } + ); return $settings_form; } diff --git a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php index 73ed8b43959..39139d937df 100644 --- a/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php +++ b/core/modules/field_ui/src/Form/EntityViewDisplayEditForm.php @@ -169,15 +169,18 @@ class EntityViewDisplayEditForm extends EntityDisplayFormBase { $settings_form = []; // Invoke hook_field_formatter_third_party_settings_form(), keying resulting // subforms by module name. - foreach ($this->moduleHandler->getImplementations('field_formatter_third_party_settings_form') as $module) { - $settings_form[$module] = $this->moduleHandler->invoke($module, 'field_formatter_third_party_settings_form', [ - $plugin, - $field_definition, - $this->entity->getMode(), - $form, - $form_state, - ]); - } + $this->moduleHandler->invokeAllWith( + 'field_formatter_third_party_settings_form', + function (callable $hook, string $module) use (&$settings_form, &$plugin, &$field_definition, &$form, &$form_state) { + $settings_form[$module] = $hook( + $plugin, + $field_definition, + $this->entity->getMode(), + $form, + $form_state, + ); + } + ); return $settings_form; } diff --git a/core/modules/help/src/Controller/HelpController.php b/core/modules/help/src/Controller/HelpController.php index 970de141471..a19f6bf9f1b 100644 --- a/core/modules/help/src/Controller/HelpController.php +++ b/core/modules/help/src/Controller/HelpController.php @@ -128,7 +128,7 @@ class HelpController extends ControllerBase { */ public function helpPage($name) { $build = []; - if ($this->moduleHandler()->implementsHook($name, 'help')) { + if ($this->moduleHandler()->hasImplementations('help', $name)) { $module_name = $this->moduleHandler()->getName($name); $build['#title'] = $module_name; diff --git a/core/modules/help/src/Plugin/Block/HelpBlock.php b/core/modules/help/src/Plugin/Block/HelpBlock.php index 04f8bcbab6e..24cd3f3c505 100644 --- a/core/modules/help/src/Plugin/Block/HelpBlock.php +++ b/core/modules/help/src/Plugin/Block/HelpBlock.php @@ -91,20 +91,15 @@ class HelpBlock extends BlockBase implements ContainerFactoryPluginInterface { return []; } - $implementations = $this->moduleHandler->getImplementations('help'); $build = []; - $args = [ - $this->routeMatch->getRouteName(), - $this->routeMatch, - ]; - foreach ($implementations as $module) { + $this->moduleHandler->invokeAllWith('help', function (callable $hook, string $module) use (&$build) { // Don't add empty strings to $build array. - if ($help = $this->moduleHandler->invoke($module, 'help', $args)) { + if ($help = $hook($this->routeMatch->getRouteName(), $this->routeMatch)) { // Convert strings to #markup render arrays so that they will XSS admin // filtered. $build[] = is_array($help) ? $help : ['#markup' => $help]; } - } + }); return $build; } diff --git a/core/modules/help/src/Plugin/HelpSection/HookHelpSection.php b/core/modules/help/src/Plugin/HelpSection/HookHelpSection.php index 6b41fcce866..1625c90463f 100644 --- a/core/modules/help/src/Plugin/HelpSection/HookHelpSection.php +++ b/core/modules/help/src/Plugin/HelpSection/HookHelpSection.php @@ -59,10 +59,13 @@ class HookHelpSection extends HelpSectionPluginBase implements ContainerFactoryP */ public function listTopics() { $topics = []; - foreach ($this->moduleHandler->getImplementations('help') as $module) { - $title = $this->moduleHandler->getName($module); - $topics[$title] = Link::createFromRoute($title, 'help.page', ['name' => $module]); - } + $this->moduleHandler->invokeAllWith( + 'help', + function (callable $hook, string $module) use (&$topics) { + $title = $this->moduleHandler->getName($module); + $topics[$title] = Link::createFromRoute($title, 'help.page', ['name' => $module]); + } + ); // Sort topics by title, which is the array key above. ksort($topics); diff --git a/core/modules/help/tests/src/Functional/HelpTest.php b/core/modules/help/tests/src/Functional/HelpTest.php index 0c6443ea85e..0933dafcd8e 100644 --- a/core/modules/help/tests/src/Functional/HelpTest.php +++ b/core/modules/help/tests/src/Functional/HelpTest.php @@ -164,9 +164,12 @@ class HelpTest extends BrowserTestBase { protected function getModuleList() { $modules = []; $module_data = $this->container->get('extension.list.module')->getList(); - foreach (\Drupal::moduleHandler()->getImplementations('help') as $module) { - $modules[$module] = $module_data[$module]->info['name']; - } + \Drupal::moduleHandler()->invokeAllWith( + 'help', + function (callable $hook, string $module) use (&$modules, $module_data) { + $modules[$module] = $module_data[$module]->info['name']; + } + ); return $modules; } diff --git a/core/modules/help/tests/src/Functional/NoHelpTest.php b/core/modules/help/tests/src/Functional/NoHelpTest.php index 8d343f096de..0ec7615f9f1 100644 --- a/core/modules/help/tests/src/Functional/NoHelpTest.php +++ b/core/modules/help/tests/src/Functional/NoHelpTest.php @@ -44,7 +44,7 @@ class NoHelpTest extends BrowserTestBase { $this->drupalGet('admin/help'); $this->assertSession()->statusCodeEquals(200); $this->assertSession()->pageTextContains('Module overviews are provided by modules'); - $this->assertFalse(\Drupal::moduleHandler()->implementsHook('menu_test', 'help'), 'The menu_test module does not implement hook_help'); + $this->assertFalse(\Drupal::moduleHandler()->hasImplementations('help', 'menu_test'), 'The menu_test module does not implement hook_help'); // Make sure the test module menu_test does not display a help link on // admin/help. $this->assertSession()->pageTextNotContains(\Drupal::moduleHandler()->getName('menu_test')); diff --git a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php index 6225bf3aa69..a17bd3d510f 100644 --- a/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php +++ b/core/modules/jsonapi/src/Access/TemporaryQueryGuard.php @@ -442,14 +442,17 @@ class TemporaryQueryGuard { // hook_jsonapi_ENTITY_TYPE_filter_access() for each module and merge its // results with the combined results. foreach (['jsonapi_entity_filter_access', 'jsonapi_' . $entity_type->id() . '_filter_access'] as $hook) { - foreach (static::$moduleHandler->getImplementations($hook) as $module) { - $module_access_results = static::$moduleHandler->invoke($module, $hook, [$entity_type, $account]); - if ($module_access_results) { - foreach ($module_access_results as $subset => $access_result) { - $combined_access_results[$subset] = $combined_access_results[$subset]->orIf($access_result); + static::$moduleHandler->invokeAllWith( + $hook, + function (callable $hook, string $module) use (&$combined_access_results, $entity_type, $account) { + $module_access_results = $hook($entity_type, $account); + if ($module_access_results) { + foreach ($module_access_results as $subset => $access_result) { + $combined_access_results[$subset] = $combined_access_results[$subset]->orIf($access_result); + } } } - } + ); } return $combined_access_results; diff --git a/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php b/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php index b3b7b919ae2..59adfd861ba 100644 --- a/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php +++ b/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php @@ -311,15 +311,18 @@ class FieldBlock extends BlockBase implements ContextAwarePluginInterface, Conta $settings_form = []; // Invoke hook_field_formatter_third_party_settings_form(), keying resulting // subforms by module name. - foreach ($this->moduleHandler->getImplementations('field_formatter_third_party_settings_form') as $module) { - $settings_form[$module] = $this->moduleHandler->invoke($module, 'field_formatter_third_party_settings_form', [ - $plugin, - $field_definition, - EntityDisplayBase::CUSTOM_MODE, - $form, - $form_state, - ]); - } + $this->moduleHandler->invokeAllWith( + 'field_formatter_third_party_settings_form', + function (callable $hook, string $module) use (&$settings_form, $plugin, $field_definition, $form, $form_state) { + $settings_form[$module] = $hook( + $plugin, + $field_definition, + EntityDisplayBase::CUSTOM_MODE, + $form, + $form_state, + ); + } + ); return $settings_form; } diff --git a/core/modules/node/node.install b/core/modules/node/node.install index 676e05e2b6c..154ae4d9ded 100644 --- a/core/modules/node/node.install +++ b/core/modules/node/node.install @@ -19,7 +19,7 @@ function node_requirements($phase) { // in the {node_access} table, or if there are modules that // implement hook_node_grants(). $grant_count = \Drupal::entityTypeManager()->getAccessControlHandler('node')->countGrants(); - if ($grant_count != 1 || count(\Drupal::moduleHandler()->getImplementations('node_grants')) > 0) { + if ($grant_count != 1 || \Drupal::moduleHandler()->hasImplementations('node_grants')) { $value = \Drupal::translation()->formatPlural($grant_count, 'One permission in use', '@count permissions in use', ['@count' => $grant_count]); } else { diff --git a/core/modules/node/node.module b/core/modules/node/node.module index f85e839584c..7e194d91b6a 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -913,7 +913,7 @@ function node_access_view_all_nodes($account = NULL) { } // If no modules implement the node access system, access is always TRUE. - if (!\Drupal::moduleHandler()->getImplementations('node_grants')) { + if (!\Drupal::moduleHandler()->hasImplementations('node_grants')) { $access[$account->id()] = TRUE; } else { @@ -954,7 +954,7 @@ function node_query_node_access_alter(AlterableInterface $query) { if ($account->hasPermission('bypass node access')) { return; } - if (!count(\Drupal::moduleHandler()->getImplementations('node_grants'))) { + if (!\Drupal::moduleHandler()->hasImplementations('node_grants')) { return; } if ($op == 'view' && node_access_view_all_nodes($account)) { @@ -1066,7 +1066,7 @@ function node_access_rebuild($batch_mode = FALSE) { $access_control_handler = \Drupal::entityTypeManager()->getAccessControlHandler('node'); $access_control_handler->deleteGrants(); // Only recalculate if the site is using a node_access module. - if (count(\Drupal::moduleHandler()->getImplementations('node_grants'))) { + if (\Drupal::moduleHandler()->hasImplementations('node_grants')) { if ($batch_mode) { $batch_builder = (new BatchBuilder()) ->setTitle(t('Rebuilding content access permissions')) @@ -1194,10 +1194,10 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) { /** * Implements hook_modules_installed(). */ -function node_modules_installed($modules) { +function node_modules_installed(array $modules) { // Check if any of the newly enabled modules require the node_access table to // be rebuilt. - if (!node_access_needs_rebuild() && array_intersect($modules, \Drupal::moduleHandler()->getImplementations('node_grants'))) { + if (!node_access_needs_rebuild() && \Drupal::moduleHandler()->hasImplementations('node_grants', $modules)) { node_access_needs_rebuild(TRUE); } } @@ -1214,14 +1214,14 @@ function node_modules_uninstalled($modules) { // check whether a hook implementation function exists and do not invoke it. // Node access also needs to be rebuilt if language module is disabled to // remove any language-specific grants. - if (!node_access_needs_rebuild() && (\Drupal::moduleHandler()->implementsHook($module, 'node_grants') || $module == 'language')) { + if (!node_access_needs_rebuild() && (\Drupal::moduleHandler()->hasImplementations('node_grants', $module) || $module == 'language')) { node_access_needs_rebuild(TRUE); } } // If there remains no more node_access module, rebuilding will be // straightforward, we can do it right now. - if (node_access_needs_rebuild() && count(\Drupal::moduleHandler()->getImplementations('node_grants')) == 0) { + if (node_access_needs_rebuild() && !\Drupal::moduleHandler()->hasImplementations('node_grants')) { node_access_rebuild(); } } diff --git a/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php index 9112d81b51f..160ce143758 100644 --- a/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php +++ b/core/modules/node/src/Cache/NodeAccessGrantsCacheContext.php @@ -84,7 +84,7 @@ class NodeAccessGrantsCacheContext extends UserCacheContextBase implements Calcu public function getCacheableMetadata($operation = NULL) { $cacheable_metadata = new CacheableMetadata(); - if (!\Drupal::moduleHandler()->getImplementations('node_grants')) { + if (!\Drupal::moduleHandler()->hasImplementations('node_grants')) { return $cacheable_metadata; } diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php index 155c480b8eb..4718a7c8765 100644 --- a/core/modules/node/src/NodeGrantDatabaseStorage.php +++ b/core/modules/node/src/NodeGrantDatabaseStorage.php @@ -66,7 +66,7 @@ class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface { // If no module implements the hook or the node does not have an id there is // no point in querying the database for access grants. - if (!$this->moduleHandler->getImplementations('node_grants') || !$node->id()) { + if (!$this->moduleHandler->hasImplementations('node_grants') || !$node->id()) { // Return the equivalent of the default grant, defined by // self::writeDefault(). if ($operation === 'view') { @@ -208,7 +208,7 @@ class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface { $query->execute(); } // Only perform work when node_access modules are active. - if (!empty($grants) && count($this->moduleHandler->getImplementations('node_grants'))) { + if (!empty($grants) && $this->moduleHandler->hasImplementations('node_grants')) { $query = $this->database->insert('node_access')->fields(['nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete']); // If we have defined a granted langcode, use it. But if not, add a grant // for every language this node is translated to. diff --git a/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php b/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php index 5fdb3ff859e..f40046c2931 100644 --- a/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php +++ b/core/modules/node/src/Plugin/EntityReferenceSelection/NodeSelection.php @@ -28,7 +28,7 @@ class NodeSelection extends DefaultSelection { // 'unpublished'. We need to do that as long as there are no access control // modules in use on the site. As long as one access control module is there, // it is supposed to handle this check. - if (!$this->currentUser->hasPermission('bypass node access') && !count($this->moduleHandler->getImplementations('node_grants'))) { + if (!$this->currentUser->hasPermission('bypass node access') && !$this->moduleHandler->hasImplementations('node_grants')) { $query->condition('status', NodeInterface::PUBLISHED); } return $query; @@ -53,7 +53,7 @@ class NodeSelection extends DefaultSelection { public function validateReferenceableNewEntities(array $entities) { $entities = parent::validateReferenceableNewEntities($entities); // Mirror the conditions checked in buildEntityQuery(). - if (!$this->currentUser->hasPermission('bypass node access') && !count($this->moduleHandler->getImplementations('node_grants'))) { + if (!$this->currentUser->hasPermission('bypass node access') && !$this->moduleHandler->hasImplementations('node_grants')) { $entities = array_filter($entities, function ($node) { /** @var \Drupal\node\NodeInterface $node */ return $node->isPublished(); diff --git a/core/modules/node/tests/src/Functional/NodeAccessCacheabilityWithNodeGrants.php b/core/modules/node/tests/src/Functional/NodeAccessCacheabilityWithNodeGrants.php index d7910f4ff8f..a9a467d50dd 100644 --- a/core/modules/node/tests/src/Functional/NodeAccessCacheabilityWithNodeGrants.php +++ b/core/modules/node/tests/src/Functional/NodeAccessCacheabilityWithNodeGrants.php @@ -43,8 +43,7 @@ class NodeAccessCacheabilityWithNodeGrants extends BrowserTestBase { // Check that at least one module implements hook_node_grants() as this test // only tests this case. // @see \node_test_node_grants() - $node_grants_implementations = \Drupal::moduleHandler()->getImplementations('node_grants'); - $this->assertNotEmpty($node_grants_implementations); + $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('node_grants')); // Create an unpublished node. $referenced = $this->createNode(['status' => FALSE]); diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module index 39209dd0222..ad8af35bef5 100644 --- a/core/modules/rdf/rdf.module +++ b/core/modules/rdf/rdf.module @@ -113,17 +113,20 @@ function rdf_get_namespaces() { $namespaces = []; // In order to resolve duplicate namespaces by using the earliest defined // namespace, do not use \Drupal::moduleHandler()->invokeAll(). - foreach (\Drupal::moduleHandler()->getImplementations('rdf_namespaces') as $module) { - $function = $module . '_rdf_namespaces'; - foreach ($function() as $prefix => $namespace) { - if (array_key_exists($prefix, $namespaces) && $namespace !== $namespaces[$prefix]) { - throw new Exception("Tried to map '$prefix' to '$namespace', but '$prefix' is already mapped to '{$namespaces[$prefix]}'."); - } - else { - $namespaces[$prefix] = $namespace; + \Drupal::moduleHandler()->invokeAllWith( + 'rdf_namespaces', + function (callable $hook, string $module) use (&$namespaces) { + $namespacesFromHook = $hook(); + foreach ($namespacesFromHook as $prefix => $namespace) { + if (array_key_exists($prefix, $namespaces) && $namespace !== $namespaces[$prefix]) { + throw new Exception("Tried to map '$prefix' to '$namespace', but '$prefix' is already mapped to '{$namespaces[$prefix]}'."); + } + else { + $namespaces[$prefix] = $namespace; + } } } - } + ); return $namespaces; } diff --git a/core/modules/search/src/SearchTextProcessor.php b/core/modules/search/src/SearchTextProcessor.php index ce3fbec0722..dfbcc8adad8 100644 --- a/core/modules/search/src/SearchTextProcessor.php +++ b/core/modules/search/src/SearchTextProcessor.php @@ -117,9 +117,12 @@ class SearchTextProcessor implements SearchTextProcessorInterface { * Language code for the language of $text, if known. */ protected function invokePreprocess(string &$text, ?string $langcode = NULL): void { - foreach ($this->moduleHandler->getImplementations('search_preprocess') as $module) { - $text = $this->moduleHandler->invoke($module, 'search_preprocess', [$text, $langcode]); - } + $this->moduleHandler->invokeAllWith( + 'search_preprocess', + function (callable $hook, string $module) use (&$text, &$langcode) { + $text = $hook($text, $langcode); + } + ); } /** diff --git a/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php b/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php index a21c33af9c4..00d27976d90 100644 --- a/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php +++ b/core/modules/shortcut/src/Form/ShortcutSetDeleteForm.php @@ -63,7 +63,7 @@ class ShortcutSetDeleteForm extends EntityDeleteForm { // Also, if a module implements hook_shortcut_default_set(), it's possible // that this set is being used as a default set. Add a message about that too. - if ($this->moduleHandler->getImplementations('shortcut_default_set')) { + if ($this->moduleHandler->hasImplementations('shortcut_default_set')) { $info .= '

' . $this->t('If you have chosen this shortcut set as the default for some or all users, they may also be affected by deleting it.') . '

'; } diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php index c7c18f1ec68..d0ec6f5b036 100644 --- a/core/modules/system/src/Form/ModulesListForm.php +++ b/core/modules/system/src/Form/ModulesListForm.php @@ -278,7 +278,7 @@ class ModulesListForm extends FormBase { // Generate link for module's help page. Assume that if a hook_help() // implementation exists then the module provides an overview page, rather // than checking to see if the page exists, which is costly. - if ($this->moduleHandler->moduleExists('help') && $module->status && in_array($module->getName(), $this->moduleHandler->getImplementations('help'))) { + if ($this->moduleHandler->moduleExists('help') && $module->status && $this->moduleHandler->hasImplementations('help', $module->getName())) { $row['links']['help'] = [ '#type' => 'link', '#title' => $this->t('Help for @module', ['@module' => $module->info['name']]), diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 37cde7d4a14..d896ffd2ac7 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -1346,19 +1346,22 @@ function system_requirements($phase) { /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */ $update_registry = \Drupal::service('update.update_hook_registry'); $module_list = []; - foreach ($module_handler->getImplementations('update_last_removed') as $module) { - $last_removed = $module_handler->invoke($module, 'update_last_removed'); - if ($last_removed && $last_removed > $update_registry->getInstalledVersion($module)) { + $module_handler->invokeAllWith( + 'update_last_removed', + function (callable $hook, string $module) use (&$module_list, $update_registry, $module_extension_list) { + $last_removed = $hook(); + if ($last_removed && $last_removed > $update_registry->getInstalledVersion($module)) { - /** @var \Drupal\Core\Extension\Extension $module_info */ - $module_info = $module_extension_list->get($module); - $module_list[$module] = [ - 'name' => $module_info->info['name'], - 'last_removed' => $last_removed, - 'installed_version' => $update_registry->getInstalledVersion($module), - ]; + /** @var \Drupal\Core\Extension\Extension $module_info */ + $module_info = $module_extension_list->get($module); + $module_list[$module] = [ + 'name' => $module_info->info['name'], + 'last_removed' => $last_removed, + 'installed_version' => $update_registry->getInstalledVersion($module), + ]; + } } - } + ); // If system or workspaces is in the list then only show a specific message // for Drupal core. diff --git a/core/modules/system/tests/modules/module_test/module_test.file.inc b/core/modules/system/tests/modules/module_test/module_test.file.inc index b40e0f34537..a6b7b372656 100644 --- a/core/modules/system/tests/modules/module_test/module_test.file.inc +++ b/core/modules/system/tests/modules/module_test/module_test.file.inc @@ -2,7 +2,7 @@ /** * @file - * A file to test \Drupal::moduleHandler()->getImplementations() loading includes. + * A file to test \Drupal::moduleHandler()->getImplementationInfo() loading includes. */ /** diff --git a/core/modules/system/tests/modules/plugin_test/plugin_test.module b/core/modules/system/tests/modules/plugin_test/plugin_test.module index 147e0526161..0bc36c0fc6b 100644 --- a/core/modules/system/tests/modules/plugin_test/plugin_test.module +++ b/core/modules/system/tests/modules/plugin_test/plugin_test.module @@ -5,6 +5,20 @@ * Helper module for the plugin tests. */ +use Drupal\plugin_test\Plugin\plugin_test\fruit\Apple; + +/** + * Implements hook_test_plugin_info(). + */ +function plugin_test_test_plugin_info() { + return [ + 'apple' => [ + 'id' => 'apple', + 'class' => Apple::class, + ], + ]; +} + /** * Implements hook_plugin_test_alter(). */ diff --git a/core/modules/user/tests/src/Unit/PermissionHandlerTest.php b/core/modules/user/tests/src/Unit/PermissionHandlerTest.php index 369b4f299a2..050895a0732 100644 --- a/core/modules/user/tests/src/Unit/PermissionHandlerTest.php +++ b/core/modules/user/tests/src/Unit/PermissionHandlerTest.php @@ -125,10 +125,6 @@ EOF EOF ); $modules = ['module_a', 'module_b', 'module_c']; - $this->moduleHandler->expects($this->any()) - ->method('getImplementations') - ->with('permission') - ->willReturn([]); $this->moduleHandler->expects($this->any()) ->method('getModuleList') @@ -248,11 +244,6 @@ EOF $modules = ['module_a', 'module_b', 'module_c']; - $this->moduleHandler->expects($this->any()) - ->method('getImplementations') - ->with('permission') - ->willReturn([]); - $this->moduleHandler->expects($this->any()) ->method('getModuleList') ->willReturn(array_flip($modules)); @@ -300,11 +291,6 @@ EOF $modules = ['module_a']; - $this->moduleHandler->expects($this->any()) - ->method('getImplementations') - ->with('permission') - ->willReturn([]); - $this->moduleHandler->expects($this->any()) ->method('getModuleList') ->willReturn(array_flip($modules)); diff --git a/core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php b/core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php index 0767c5ca6a5..0fea2899a1a 100644 --- a/core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php +++ b/core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php @@ -119,9 +119,6 @@ class UserAccessControlHandlerTest extends UnitTestCase { $this->accessControlHandler = new UserAccessControlHandler($entity_type); $module_handler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface'); - $module_handler->expects($this->any()) - ->method('getImplementations') - ->will($this->returnValue([])); $this->accessControlHandler->setModuleHandler($module_handler); $this->items = $this->getMockBuilder('Drupal\Core\Field\FieldItemList') diff --git a/core/modules/views/src/ViewsData.php b/core/modules/views/src/ViewsData.php index bcbab7bce30..8474823cb06 100644 --- a/core/modules/views/src/ViewsData.php +++ b/core/modules/views/src/ViewsData.php @@ -231,10 +231,9 @@ class ViewsData { return $data->data; } else { - $modules = $this->moduleHandler->getImplementations('views_data'); $data = []; - foreach ($modules as $module) { - $views_data = $this->moduleHandler->invoke($module, 'views_data'); + $this->moduleHandler->invokeAllWith('views_data', function (callable $hook, string $module) use (&$data) { + $views_data = $hook(); // Set the provider key for each base table. foreach ($views_data as &$table) { if (isset($table['table']) && !isset($table['table']['provider'])) { @@ -242,7 +241,7 @@ class ViewsData { } } $data = NestedArray::mergeDeep($data, $views_data); - } + }); $this->moduleHandler->alter('views_data', $data); $this->processEntityTypes($data); diff --git a/core/modules/views/tests/modules/views_test_data/views_test_data.views.inc b/core/modules/views/tests/modules/views_test_data/views_test_data.views.inc index 11159412b4a..cab4f98560c 100644 --- a/core/modules/views/tests/modules/views_test_data/views_test_data.views.inc +++ b/core/modules/views/tests/modules/views_test_data/views_test_data.views.inc @@ -29,8 +29,9 @@ function views_test_data_views_data() { /** * Implements hook_views_data_alter(). */ -function views_test_data_views_data_alter() { +function views_test_data_views_data_alter(array &$data) { \Drupal::state()->set('views_hook_test_views_data_alter', TRUE); + \Drupal::state()->set('views_hook_test_views_data_alter_data', $data); } /** diff --git a/core/modules/views/tests/src/Kernel/ViewsHooksTest.php b/core/modules/views/tests/src/Kernel/ViewsHooksTest.php index a16448a20dd..bbca39f28d9 100644 --- a/core/modules/views/tests/src/Kernel/ViewsHooksTest.php +++ b/core/modules/views/tests/src/Kernel/ViewsHooksTest.php @@ -67,7 +67,7 @@ class ViewsHooksTest extends ViewsKernelTestBase { // Test each hook is found in the implementations array and is invoked. foreach (static::$hooks as $hook => $type) { - $this->assertTrue($this->moduleHandler->implementsHook('views_test_data', $hook), new FormattableMarkup('The hook @hook was registered.', ['@hook' => $hook])); + $this->assertTrue($this->moduleHandler->hasImplementations($hook, 'views_test_data'), new FormattableMarkup('The hook @hook was registered.', ['@hook' => $hook])); if ($hook == 'views_post_render') { $this->moduleHandler->invoke('views_test_data', $hook, [$view, &$view->display_handler->output, $view->display_handler->getPlugin('cache')]); @@ -81,7 +81,7 @@ class ViewsHooksTest extends ViewsKernelTestBase { case 'alter': $data = []; - $this->moduleHandler->invoke('views_test_data', $hook, [$data]); + $this->moduleHandler->alter($hook, $data); break; default: diff --git a/core/modules/views/tests/src/Unit/ViewsDataTest.php b/core/modules/views/tests/src/Unit/ViewsDataTest.php index 8a501ec7898..30233553cea 100644 --- a/core/modules/views/tests/src/Unit/ViewsDataTest.php +++ b/core/modules/views/tests/src/Unit/ViewsDataTest.php @@ -131,16 +131,13 @@ class ViewsDataTest extends UnitTestCase { * * @return \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected function setupMockedModuleHandler() { - $views_data = $this->viewsData(); - $this->moduleHandler->expects($this->once()) - ->method('getImplementations') + protected function setupMockedModuleHandler(): void { + $this->moduleHandler->expects($this->atLeastOnce()) + ->method('invokeAllWith') ->with('views_data') - ->willReturn(['views_test_data']); - $this->moduleHandler->expects($this->once()) - ->method('invoke') - ->with('views_test_data', 'views_data') - ->willReturn($views_data); + ->willReturnCallback(function (string $hook, callable $callback) { + $callback(\Closure::fromCallable([$this, 'viewsData']), 'views_test_data'); + }); } /** @@ -213,13 +210,11 @@ class ViewsDataTest extends UnitTestCase { // Views data should be invoked twice due to the clear call. $this->moduleHandler->expects($this->exactly(2)) - ->method('getImplementations') + ->method('invokeAllWith') ->with('views_data') - ->willReturn(['views_test_data']); - $this->moduleHandler->expects($this->exactly(2)) - ->method('invoke') - ->with('views_test_data', 'views_data') - ->willReturn($this->viewsData()); + ->willReturnCallback(function ($hook, $callback) { + $callback(\Closure::fromCallable([$this, 'viewsData']), 'views_test_data'); + }); $this->moduleHandler->expects($this->exactly(2)) ->method('alter') ->with('views_data', $expected_views_data); @@ -403,7 +398,7 @@ class ViewsDataTest extends UnitTestCase { public function testCacheCallsWithSameTableMultipleTimesAndWarmCache() { $expected_views_data = $this->viewsDataWithProvider(); $this->moduleHandler->expects($this->never()) - ->method('getImplementations'); + ->method('invokeAllWith'); // Setup a warm cache backend for a single table. $this->cacheBackend->expects($this->once()) @@ -433,7 +428,7 @@ class ViewsDataTest extends UnitTestCase { public function testCacheCallsWithWarmCacheAndDifferentTable() { $expected_views_data = $this->viewsDataWithProvider(); $this->moduleHandler->expects($this->never()) - ->method('getImplementations'); + ->method('invokeAllWith'); // Setup a warm cache backend for a single table. $this->cacheBackend->expects($this->exactly(2)) @@ -472,7 +467,7 @@ class ViewsDataTest extends UnitTestCase { $expected_views_data = $this->viewsDataWithProvider(); $non_existing_table = $this->randomMachineName(); $this->moduleHandler->expects($this->never()) - ->method('getImplementations'); + ->method('invokeAllWith'); // Setup a warm cache backend for a single table. $this->cacheBackend->expects($this->exactly(2)) @@ -511,7 +506,7 @@ class ViewsDataTest extends UnitTestCase { public function testCacheCallsWithWarmCacheForInvalidTable() { $non_existing_table = $this->randomMachineName(); $this->moduleHandler->expects($this->never()) - ->method('getImplementations'); + ->method('invokeAllWith'); // Setup a warm cache backend for a single table. $this->cacheBackend->expects($this->once()) @@ -564,7 +559,7 @@ class ViewsDataTest extends UnitTestCase { public function testCacheCallsWithWarmCacheAndGetAllTables() { $expected_views_data = $this->viewsDataWithProvider(); $this->moduleHandler->expects($this->never()) - ->method('getImplementations'); + ->method('invokeAllWith'); // Setup a warm cache backend for a single table. $this->cacheBackend->expects($this->once()) diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php index d292a27fe20..07a20d1fa34 100644 --- a/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php +++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php @@ -42,10 +42,10 @@ class ModuleImplementsAlterTest extends KernelTestBase { $this->assertArrayHasKey('module_test', \Drupal::moduleHandler()->getModuleList()); - $this->assertContains('module_test', \Drupal::moduleHandler()->getImplementations('modules_installed'), + $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('modules_installed', 'module_test'), 'module_test implements hook_modules_installed().'); - $this->assertContains('module_test', \Drupal::moduleHandler()->getImplementations('module_implements_alter'), + $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('module_implements_alter', 'module_test'), 'module_test implements hook_module_implements_alter().'); // Assert that module_test.implementations.inc is not included yet. @@ -55,7 +55,7 @@ class ModuleImplementsAlterTest extends KernelTestBase { // Trigger hook discovery for hook_altered_test_hook(). // Assert that module_test_module_implements_alter(*, 'altered_test_hook') // has added an implementation. - $this->assertContains('module_test', \Drupal::moduleHandler()->getImplementations('altered_test_hook'), + $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('altered_test_hook', 'module_test'), 'module_test implements hook_altered_test_hook().'); // Assert that module_test.implementations.inc was included as part of the process. @@ -78,7 +78,7 @@ class ModuleImplementsAlterTest extends KernelTestBase { // Trigger hook discovery. $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('An invalid implementation module_test_unimplemented_test_hook was added by hook_module_implements_alter()'); - \Drupal::moduleHandler()->getImplementations('unimplemented_test_hook'); + \Drupal::moduleHandler()->hasImplementations('unimplemented_test_hook'); } } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index 8f237c60e78..edf45b37dc9 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -526,9 +526,6 @@ class ConfigEntityStorageTest extends UnitTestCase { ->willReturn($config_object->reveal()); $this->configFactory->rename(Argument::cetera())->shouldNotBeCalled(); - $this->moduleHandler->getImplementations('entity_load')->willReturn([]); - $this->moduleHandler->getImplementations('test_entity_type_load')->willReturn([]); - $this->entityQuery->condition('uuid', 'baz')->willReturn($this->entityQuery); $this->entityQuery->execute()->willReturn(['foo']); @@ -558,9 +555,6 @@ class ConfigEntityStorageTest extends UnitTestCase { $this->configFactory->loadMultiple(['the_provider.the_config_prefix.foo']) ->willReturn([$config_object->reveal()]); - $this->moduleHandler->getImplementations('entity_load')->willReturn([]); - $this->moduleHandler->getImplementations('test_entity_type_load')->willReturn([]); - $entity = $this->entityStorage->load('foo'); $this->assertInstanceOf(EntityInterface::class, $entity); $this->assertSame('foo', $entity->id()); @@ -598,9 +592,6 @@ class ConfigEntityStorageTest extends UnitTestCase { $this->configFactory->loadMultiple(['the_provider.the_config_prefix.foo', 'the_provider.the_config_prefix.bar']) ->willReturn([$foo_config_object->reveal(), $bar_config_object->reveal()]); - $this->moduleHandler->getImplementations('entity_load')->willReturn([]); - $this->moduleHandler->getImplementations('test_entity_type_load')->willReturn([]); - $entities = $this->entityStorage->loadMultiple(); $expected['foo'] = 'foo'; $expected['bar'] = 'bar'; @@ -629,9 +620,6 @@ class ConfigEntityStorageTest extends UnitTestCase { $this->configFactory->loadMultiple(['the_provider.the_config_prefix.foo']) ->willReturn([$config_object->reveal()]); - $this->moduleHandler->getImplementations('entity_load')->willReturn([]); - $this->moduleHandler->getImplementations('test_entity_type_load')->willReturn([]); - $entities = $this->entityStorage->loadMultiple(['foo']); $this->assertContainsOnlyInstancesOf(EntityInterface::class, $entities); foreach ($entities as $id => $entity) { @@ -703,7 +691,6 @@ class ConfigEntityStorageTest extends UnitTestCase { * @covers ::doDelete */ public function testDeleteNothing() { - $this->moduleHandler->getImplementations(Argument::cetera())->shouldNotBeCalled(); $this->moduleHandler->invokeAll(Argument::cetera())->shouldNotBeCalled(); $this->configFactory->get(Argument::cetera())->shouldNotBeCalled(); diff --git a/core/tests/Drupal/Tests/Core/Entity/Access/EntityFormDisplayAccessControlHandlerTest.php b/core/tests/Drupal/Tests/Core/Entity/Access/EntityFormDisplayAccessControlHandlerTest.php index 154a1b0c618..56a4b27af49 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Access/EntityFormDisplayAccessControlHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Access/EntityFormDisplayAccessControlHandlerTest.php @@ -139,7 +139,7 @@ class EntityFormDisplayAccessControlHandlerTest extends UnitTestCase { $this->moduleHandler = $this->createMock(ModuleHandlerInterface::class); $this->moduleHandler ->expects($this->any()) - ->method('getImplementations') + ->method('invokeAllWith') ->will($this->returnValue([])); $this->moduleHandler ->expects($this->any()) diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php index c41bbcd47df..df404f8e7fc 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityFieldManagerTest.php @@ -272,9 +272,17 @@ class EntityFieldManagerTest extends UnitTestCase { 'field_storage' => $field_storage_definition->reveal(), ]; - $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([]); - $this->moduleHandler->getImplementations('entity_field_storage_info')->willReturn(['example_module']); - $this->moduleHandler->invoke('example_module', 'entity_field_storage_info', [$this->entityType])->willReturn($definitions); + $this->moduleHandler->invokeAllWith('entity_base_field_info', Argument::any()); + $this->moduleHandler->invokeAllWith('entity_field_storage_info', Argument::any()) + ->will(function ($arguments) use ($definitions) { + [$hook, $callback] = $arguments; + $callback( + function () use ($definitions) { + return $definitions; + }, + 'example_module', + ); + }); $this->moduleHandler->alter('entity_field_storage_info', $definitions, $this->entityType)->willReturn(NULL); $expected = [ @@ -436,8 +444,16 @@ class EntityFieldManagerTest extends UnitTestCase { $definitions = ['field_storage' => $field_storage_definition->reveal()]; - $this->moduleHandler->getImplementations('entity_field_storage_info')->willReturn(['example_module']); - $this->moduleHandler->invoke('example_module', 'entity_field_storage_info', [$this->entityType])->willReturn($definitions); + $this->moduleHandler->invokeAllWith('entity_field_storage_info', Argument::any()) + ->will(function ($arguments) use ($definitions) { + [$hook, $callback] = $arguments; + $callback( + function () use ($definitions) { + return $definitions; + }, + 'example_module', + ); + }); $this->moduleHandler->alter('entity_field_storage_info', $definitions, $this->entityType)->willReturn(NULL); $expected = [ @@ -503,9 +519,16 @@ class EntityFieldManagerTest extends UnitTestCase { $field_definition->setTargetBundle(NULL)->shouldBeCalled(); $field_definition->setTargetBundle('test_bundle')->shouldBeCalled(); - $this->moduleHandler->getImplementations(Argument::type('string'))->willReturn([$module]); - $this->moduleHandler->invoke($module, 'entity_base_field_info', [$this->entityType])->willReturn([$field_definition->reveal()]); - $this->moduleHandler->invoke($module, 'entity_bundle_field_info', Argument::type('array'))->willReturn([$field_definition->reveal()]); + $this->moduleHandler->invokeAllWith(Argument::type('string'), Argument::any()) + ->will(function ($arguments) use ($field_definition, $module) { + [$hook, $callback] = $arguments; + $callback( + function () use ($field_definition) { + return [$field_definition->reveal()]; + }, + $module, + ); + }); $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_bundle'); } @@ -543,7 +566,7 @@ class EntityFieldManagerTest extends UnitTestCase { $entity_class::$bundleFieldDefinitions = []; if (!$custom_invoke_all) { - $this->moduleHandler->getImplementations(Argument::cetera())->willReturn([]); + $this->moduleHandler->invokeAllWith(Argument::cetera(), Argument::cetera()); } // Mock the base field definition override. @@ -683,7 +706,7 @@ class EntityFieldManagerTest extends UnitTestCase { 'first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle', ])->shouldBeCalled(); - $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([]); + $this->moduleHandler->invokeAllWith('entity_base_field_info', Argument::any()); $expected = [ 'test_entity_type' => [ @@ -738,7 +761,7 @@ class EntityFieldManagerTest extends UnitTestCase { 'first_bundle' => 'first_bundle', 'second_bundle' => 'second_bundle', ])->shouldBeCalled(); - $this->moduleHandler->getImplementations('entity_base_field_info')->willReturn([])->shouldBeCalled(); + $this->moduleHandler->invokeAllWith('entity_base_field_info', Argument::any())->shouldBeCalled(); // Define an ID field definition as a base field. $id_definition = $this->prophesize(FieldDefinitionInterface::class); diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeBundleInfoTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeBundleInfoTest.php index 94dad480862..591f6ded76b 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeBundleInfoTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeBundleInfoTest.php @@ -80,7 +80,6 @@ class EntityTypeBundleInfoTest extends UnitTestCase { parent::setUp(); $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class); - $this->moduleHandler->getImplementations('entity_type_build')->willReturn([]); $this->moduleHandler->alter('entity_type', Argument::type('array'))->willReturn(NULL); $this->cacheBackend = $this->prophesize(CacheBackendInterface::class); diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php index 4040365ff08..edb6240e1fa 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeManagerTest.php @@ -80,8 +80,6 @@ class EntityTypeManagerTest extends UnitTestCase { parent::setUp(); $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class); - $this->moduleHandler->getImplementations('entity_type_build')->willReturn([]); - $this->moduleHandler->alter('entity_type', Argument::type('array'))->willReturn(NULL); $this->cacheBackend = $this->prophesize(CacheBackendInterface::class); $this->translationManager = $this->prophesize(TranslationInterface::class); diff --git a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php index 62c9d27935a..27784bce4c8 100644 --- a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php @@ -289,11 +289,6 @@ class KeyValueEntityStorageTest extends UnitTestCase { ->will($this->returnValue([['id' => 'foo']])); $this->keyValueStore->expects($this->never()) ->method('delete'); - - $this->moduleHandler->expects($this->exactly(2)) - ->method('getImplementations') - ->withConsecutive(['entity_load'], ['test_entity_type_load']) - ->will($this->returnValue([])); $this->moduleHandler->expects($this->exactly(4)) ->method('invokeAll') ->withConsecutive( @@ -357,10 +352,6 @@ class KeyValueEntityStorageTest extends UnitTestCase { ->will($this->returnValue(get_class($entity))); $this->setUpKeyValueEntityStorage(); - $this->moduleHandler->expects($this->exactly(2)) - ->method('getImplementations') - ->withConsecutive(['entity_load'], ['test_entity_type_load']) - ->will($this->returnValue([])); $expected = ['id' => 'foo']; $entity->expects($this->once()) ->method('toArray') @@ -480,10 +471,6 @@ class KeyValueEntityStorageTest extends UnitTestCase { ->method('getMultiple') ->with(['foo']) ->will($this->returnValue([['id' => 'foo']])); - $this->moduleHandler->expects($this->exactly(2)) - ->method('getImplementations') - ->withConsecutive(['entity_load'], ['test_entity_type_load']) - ->will($this->returnValue([])); $entity = $this->entityStorage->load('foo'); $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity); $this->assertSame('foo', $entity->id()); @@ -499,8 +486,6 @@ class KeyValueEntityStorageTest extends UnitTestCase { ->method('getMultiple') ->with(['foo']) ->will($this->returnValue([])); - $this->moduleHandler->expects($this->never()) - ->method('getImplementations'); $entity = $this->entityStorage->load('foo'); $this->assertNull($entity); } @@ -522,10 +507,6 @@ class KeyValueEntityStorageTest extends UnitTestCase { $this->keyValueStore->expects($this->once()) ->method('getAll') ->will($this->returnValue([['id' => 'foo'], ['id' => 'bar']])); - $this->moduleHandler->expects($this->exactly(2)) - ->method('getImplementations') - ->withConsecutive(['entity_load'], ['test_entity_type_load']) - ->will($this->returnValue([])); $entities = $this->entityStorage->loadMultiple(); foreach ($entities as $id => $entity) { $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity); @@ -552,10 +533,6 @@ class KeyValueEntityStorageTest extends UnitTestCase { ->method('getMultiple') ->with(['foo']) ->will($this->returnValue([['id' => 'foo']])); - $this->moduleHandler->expects($this->exactly(2)) - ->method('getImplementations') - ->withConsecutive(['entity_load'], ['test_entity_type_load']) - ->will($this->returnValue([])); $entities = $this->entityStorage->loadMultiple(['foo']); foreach ($entities as $id => $entity) { $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity); diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php index 7df455288d2..2f5db5ad0a2 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageTest.php @@ -1444,7 +1444,7 @@ class SqlContentEntityStorageTest extends UnitTestCase { */ protected function setUpModuleHandlerNoImplementations() { $this->moduleHandler->expects($this->any()) - ->method('getImplementations') + ->method('invokeAllWith') ->willReturnMap([ ['entity_load', []], [$this->entityTypeId . '_load', []], diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php index 4d75e43a47c..355507a5ffb 100644 --- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php @@ -306,36 +306,78 @@ class ModuleHandlerTest extends UnitTestCase { /** * Tests implementations methods when module is enabled. * - * @covers ::implementsHook + * @covers ::hasImplementations * @covers ::loadAllIncludes */ public function testImplementsHookModuleEnabled() { $module_handler = $this->getModuleHandler(); - $this->assertTrue($module_handler->implementsHook('module_handler_test', 'hook'), 'Installed module implementation found.'); + $this->assertTrue($module_handler->hasImplementations('hook', 'module_handler_test'), 'Installed module implementation found.'); $module_handler->addModule('module_handler_test_added', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_added'); - $this->assertTrue($module_handler->implementsHook('module_handler_test_added', 'hook'), 'Runtime added module with implementation in include found.'); + $this->assertTrue($module_handler->hasImplementations('hook', 'module_handler_test_added'), 'Runtime added module with implementation in include found.'); $module_handler->addModule('module_handler_test_no_hook', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_no_hook'); - $this->assertFalse($module_handler->implementsHook('module_handler_test_no_hook', 'hook', [TRUE]), 'Missing implementation not found.'); + $this->assertFalse($module_handler->hasImplementations('hook', 'module_handler_test_no_hook'), 'Missing implementation not found.'); } /** - * Tests getImplementations. + * Tests deprecation of the ::getImplementations method. * * @covers ::getImplementations * @covers ::getImplementationInfo * @covers ::buildImplementationInfo + * + * @group legacy */ public function testGetImplementations() { + $this->expectDeprecation('ModuleHandlerInterface::getImplementations() is deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. Instead you should use ModuleHandlerInterface::invokeAllWith() for hook invocations, or you should use ModuleHandlerInterface::hasImplementations() to determine if hooks implementations exist. See https://www.drupal.org/node/3000490'); $this->assertEquals(['module_handler_test'], $this->getModuleHandler()->getImplementations('hook')); } + /** + * Tests deprecation of the ::implementsHook method. + * + * @covers ::implementsHook + * + * @group legacy + */ + public function testImplementsHook() { + $this->expectDeprecation('ModuleHandlerInterface::implementsHook() is deprecated in drupal:9.4.0 and is removed from drupal:10.0.0. Instead you should use ModuleHandlerInterface::hasImplementations() with the $modules argument. See https://www.drupal.org/node/3000490'); + $this->assertTrue($this->getModuleHandler()->implementsHook('module_handler_test', 'hook')); + } + + /** + * Tests hasImplementations. + * + * @covers ::hasImplementations + */ + public function testHasImplementations() { + $module_handler = $this->getMockBuilder(ModuleHandler::class) + ->setConstructorArgs([$this->root, [], $this->cacheBackend]) + ->onlyMethods(['buildImplementationInfo']) + ->getMock(); + $module_handler->expects($this->exactly(2)) + ->method('buildImplementationInfo') + ->with('hook') + ->willReturnOnConsecutiveCalls( + [], + ['mymodule' => FALSE], + ); + + // ModuleHandler::buildImplementationInfo mock returns no implementations. + $this->assertFalse($module_handler->hasImplementations('hook')); + + // Reset static caches. + $module_handler->resetImplementations(); + + // ModuleHandler::buildImplementationInfo mock returns an implementation. + $this->assertTrue($module_handler->hasImplementations('hook')); + } + /** * Tests getImplementations. * - * @covers ::getImplementations - * @covers ::getImplementationInfo + * @covers ::invokeAllWith */ public function testCachedGetImplementations() { $this->cacheBackend->expects($this->exactly(1)) @@ -361,14 +403,20 @@ class ModuleHandlerTest extends UnitTestCase { $module_handler->expects($this->never())->method('buildImplementationInfo'); $module_handler->expects($this->once())->method('loadInclude'); - $this->assertEquals(['module_handler_test'], $module_handler->getImplementations('hook')); + $implementors = []; + $module_handler->invokeAllWith( + 'hook', + function (callable $hook, string $module) use (&$implementors) { + $implementors[] = $module; + } + ); + $this->assertEquals(['module_handler_test'], $implementors); } /** * Tests getImplementations. * - * @covers ::getImplementations - * @covers ::getImplementationInfo + * @covers ::invokeAllWith */ public function testCachedGetImplementationsMissingMethod() { $this->cacheBackend->expects($this->exactly(1)) @@ -398,7 +446,14 @@ class ModuleHandlerTest extends UnitTestCase { $module_handler->load('module_handler_test'); $module_handler->expects($this->never())->method('buildImplementationInfo'); - $this->assertEquals(['module_handler_test'], $module_handler->getImplementations('hook')); + $implementors = []; + $module_handler->invokeAllWith( + 'hook', + function (callable $hook, string $module) use (&$implementors) { + $implementors[] = $module; + } + ); + $this->assertEquals(['module_handler_test'], $implementors); } /** @@ -428,7 +483,7 @@ class ModuleHandlerTest extends UnitTestCase { ->expects($this->exactly(2)) ->method('set') ->with($this->logicalOr('module_implements', 'hook_info')); - $module_handler->getImplementations('hook'); + $module_handler->invokeAllWith('hook', function (callable $hook, string $module) {}); $module_handler->writeCache(); } @@ -469,7 +524,7 @@ class ModuleHandlerTest extends UnitTestCase { public function testResetImplementations() { $module_handler = $this->getModuleHandler(); // Prime caches - $module_handler->getImplementations('hook'); + $module_handler->invokeAllWith('hook', function (callable $hook, string $module) {}); $module_handler->getHookInfo(); // Reset all caches internal and external. @@ -491,7 +546,7 @@ class ModuleHandlerTest extends UnitTestCase { ->expects($this->exactly(2)) ->method('get') ->with($this->logicalOr('module_implements', 'hook_info')); - $module_handler->getImplementations('hook'); + $module_handler->invokeAllWith('hook', function (callable $hook, string $module) {}); } /** diff --git a/core/tests/Drupal/Tests/Core/Plugin/Discovery/HookDiscoveryTest.php b/core/tests/Drupal/Tests/Core/Plugin/Discovery/HookDiscoveryTest.php index 9b3e1abef6c..b6d13d43dc8 100644 --- a/core/tests/Drupal/Tests/Core/Plugin/Discovery/HookDiscoveryTest.php +++ b/core/tests/Drupal/Tests/Core/Plugin/Discovery/HookDiscoveryTest.php @@ -41,7 +41,7 @@ class HookDiscoveryTest extends UnitTestCase { */ public function testGetDefinitionsWithoutPlugins() { $this->moduleHandler->expects($this->once()) - ->method('getImplementations') + ->method('invokeAllWith') ->with('test_plugin') ->will($this->returnValue([])); @@ -54,17 +54,15 @@ class HookDiscoveryTest extends UnitTestCase { * @see \Drupal\Core\Plugin\Discovery::getDefinitions() */ public function testGetDefinitions() { - $this->moduleHandler->expects($this->once()) - ->method('getImplementations') + $this->moduleHandler->expects($this->atLeastOnce()) + ->method('invokeAllWith') ->with('test_plugin') - ->will($this->returnValue(['hook_discovery_test', 'hook_discovery_test2'])); - - $this->moduleHandler->expects($this->exactly(2)) - ->method('invoke') - ->willReturnMap([ - ['hook_discovery_test', 'test_plugin', [], $this->hookDiscoveryTestTestPlugin()], - ['hook_discovery_test2', 'test_plugin', [], $this->hookDiscoveryTest2TestPlugin()], - ]); + ->willReturnCallback(function (string $hook, callable $callback) { + $callback(\Closure::fromCallable([$this, 'hookDiscoveryTestTestPlugin']), 'hook_discovery_test'); + $callback(\Closure::fromCallable([$this, 'hookDiscoveryTest2TestPlugin']), 'hook_discovery_test2'); + }); + $this->moduleHandler->expects($this->never()) + ->method('invoke'); $definitions = $this->hookDiscovery->getDefinitions(); @@ -86,26 +84,12 @@ class HookDiscoveryTest extends UnitTestCase { */ public function testGetDefinition() { $this->moduleHandler->expects($this->exactly(4)) - ->method('getImplementations') + ->method('invokeAllWith') ->with('test_plugin') - ->will($this->returnValue(['hook_discovery_test', 'hook_discovery_test2'])); - - $this->moduleHandler->expects($this->any()) - ->method('invoke') - ->willReturnMap([ - [ - 'hook_discovery_test', - 'test_plugin', - [], - $this->hookDiscoveryTestTestPlugin(), - ], - [ - 'hook_discovery_test2', - 'test_plugin', - [], - $this->hookDiscoveryTest2TestPlugin(), - ], - ]); + ->willReturnCallback(function (string $hook, callable $callback) { + $callback(\Closure::fromCallable([$this, 'hookDiscoveryTestTestPlugin']), 'hook_discovery_test'); + $callback(\Closure::fromCallable([$this, 'hookDiscoveryTest2TestPlugin']), 'hook_discovery_test2'); + }); $this->assertNull($this->hookDiscovery->getDefinition('test_non_existent', FALSE)); @@ -129,7 +113,7 @@ class HookDiscoveryTest extends UnitTestCase { */ public function testGetDefinitionWithUnknownID() { $this->moduleHandler->expects($this->once()) - ->method('getImplementations') + ->method('invokeAllWith') ->will($this->returnValue([])); $this->expectException(PluginNotFoundException::class); diff --git a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php index 9555799a4fa..f81a8f6d748 100644 --- a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php +++ b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php @@ -137,10 +137,12 @@ class RegistryTest extends UnitTestCase { // Include the module and theme files so that hook_theme can be called. include_once $this->root . '/core/modules/system/tests/modules/theme_test/theme_test.module'; include_once $this->root . '/core/tests/fixtures/test_stable/test_stable.theme'; - $this->moduleHandler->expects($this->exactly(2)) - ->method('getImplementations') + $this->moduleHandler->expects($this->atLeastOnce()) + ->method('invokeAllWith') ->with('theme') - ->will($this->returnValue(['theme_test'])); + ->willReturnCallback(function (string $hook, callable $callback) { + $callback(function () {}, 'theme_test'); + }); $this->moduleHandler->expects($this->atLeastOnce()) ->method('getModuleList') ->willReturn([]);