Issue #2616814 by dpi, Xano, geek-merlin, Hardik_Patel_12, jofitz, alexpott, Fabianx, catch, joachim, andypost, dawehner, daffie, cweagans, neclimdul: Delegate all hook invocations to ModuleHandler

merge-requests/1968/head
catch 2022-03-29 10:52:32 +01:00
parent fb3dc749ac
commit 7e81e515da
60 changed files with 474 additions and 368 deletions

View File

@ -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']);

View File

@ -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));
}

View File

@ -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,

View File

@ -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);
}
);
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
});
}
/**

View File

@ -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);
});
}
/**

View File

@ -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);
}

View File

@ -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);

View File

@ -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.
*

View File

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

View File

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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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']);

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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 {

View File

@ -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')

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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'));

View File

@ -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;

View File

@ -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;
}

View File

@ -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 {

View File

@ -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();
}
}

View File

@ -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;
}

View File

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

View File

@ -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();

View File

@ -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]);

View File

@ -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;
}

View File

@ -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);
}
);
}
/**

View File

@ -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 .= '<p>' . $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.') . '</p>';
}

View File

@ -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 <span class="visually-hidden">for @module</span>', ['@module' => $module->info['name']]),

View File

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

View File

@ -2,7 +2,7 @@
/**
* @file
* A file to test \Drupal::moduleHandler()->getImplementations() loading includes.
* A file to test \Drupal::moduleHandler()->getImplementationInfo() loading includes.
*/
/**

View File

@ -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().
*/

View File

@ -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));

View File

@ -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')

View File

@ -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);

View File

@ -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);
}
/**

View File

@ -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:

View File

@ -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())

View File

@ -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');
}
}

View File

@ -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();

View File

@ -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())

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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', []],

View File

@ -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) {});
}
/**

View File

@ -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);

View File

@ -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([]);