diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index d38244bd65..9b85b3d252 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -720,16 +720,8 @@ protected function invokeTranslationHooks(ContentEntityInterface $entity) { */ 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); - } - // 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->invokeAll('entity_storage_load', [$entities, $this->entityTypeId]); + $this->moduleHandler->invokeAll($this->entityTypeId . '_storage_load', [$entities]); } } diff --git a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php index 0a2bb193be..a4c297ef7e 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessControlHandler.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Entity; use Drupal\Core\Access\AccessResult; +use Drupal\Core\Extension\Hook\FunctionInvoker; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Language\LanguageInterface; @@ -342,10 +343,9 @@ public function fieldAccess($operation, FieldDefinitionInterface $field_definiti // Invoke hook and collect grants/denies for field access from other // modules. Our default access flag is masked under the ':default' key. $grants = [':default' => $default]; - $hook_implementations = $this->moduleHandler()->getImplementations('entity_field_access'); - foreach ($hook_implementations as $module) { - $grants = array_merge($grants, [$module => $this->moduleHandler()->invoke($module, 'entity_field_access', [$operation, $field_definition, $account, $items])]); - } + $this->moduleHandler->invokeAllWith('entity_field_access', new FunctionInvoker(function ($hook_implementation, $module) use ($operation, $field_definition, $account, $items, &$grants) { + $grants[$module] = $hook_implementation($operation, $field_definition, $account, $items); + })); // Also allow modules to alter the returned grants/denies. $context = [ diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php index 97e6657920..57f8969f87 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php @@ -332,16 +332,8 @@ public function loadMultiple(array $ids = NULL) { protected function postLoad(array &$entities) { $entity_class = $this->entityClass; $entity_class::postLoad($this, $entities); - // 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()->invokeAll('entity_load', [$entities, $this->entityTypeId]); + $this->moduleHandler()->invokeAll($this->entityTypeId . '_load', [$entities]); } /** diff --git a/core/lib/Drupal/Core/Entity/EntityTypeManager.php b/core/lib/Drupal/Core/Entity/EntityTypeManager.php index 9d7fdc5e92..c85fc28e19 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeManager.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeManager.php @@ -7,6 +7,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\DependencyInjection\ClassResolverInterface; use Drupal\Core\Entity\Exception\InvalidLinkTemplateException; +use Drupal\Core\Extension\Hook\FunctionInvoker; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; @@ -105,12 +106,9 @@ public function processDefinition(&$definition, $plugin_id) { 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', new FunctionInvoker(function ($hook_implementation) use (&$definitions) { + $hook_implementation($definitions); + })); foreach ($definitions as $plugin_id => $definition) { $this->processDefinition($definition, $plugin_id); } diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php index 3e8365660a..6439d33a9f 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php @@ -175,7 +175,9 @@ public function getHookInfo(); /** * Determines which modules are implementing a hook. * - * @deprecated Will be removed before 9.0.0. + * @deprecated Will be removed before 9.0.0. Use the self::invoke*() methods + * instead. To pass arguments by reference or process return values, use + * self::invoke*With*(). * * @param string $hook * The name of the hook (e.g. "help" or "menu"). diff --git a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php index 51040cd0d3..561ec76c16 100644 --- a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php +++ b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php @@ -4,6 +4,7 @@ use Drupal\Component\Plugin\Discovery\DiscoveryInterface; use Drupal\Component\Plugin\Discovery\DiscoveryTrait; +use Drupal\Core\Extension\Hook\FunctionInvoker; use Drupal\Core\Extension\ModuleHandlerInterface; /** @@ -46,6 +47,13 @@ public function __construct(ModuleHandlerInterface $module_handler, $hook) { */ public function getDefinitions() { $definitions = []; + $this->moduleHandler->invokeAllWith($this->hook, new FunctionInvoker(function ($hook_implementation, $module) use (&$definitions) { + $module_definitions = $hook_implementation(); + foreach ($module_definitions as $plugin_id => $definition) { + $definition['provider'] = $module; + $definitions[$plugin_id] = $definition; + } + })); foreach ($this->moduleHandler->getImplementations($this->hook) as $module) { $result = $this->moduleHandler->invoke($module, $this->hook); foreach ($result as $plugin_id => $definition) { diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php index 7cf4b3c436..7c6785d912 100644 --- a/core/lib/Drupal/Core/Theme/Registry.php +++ b/core/lib/Drupal/Core/Theme/Registry.php @@ -330,9 +330,9 @@ protected function build() { $cache = $cached->data; } else { - foreach ($this->moduleHandler->getImplementations('theme') as $module) { + $this->moduleHandler->invokeAllWith('theme', function ($module) use (&$cache) { $this->processExtension($cache, $module, 'module', $module, $this->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/views/src/ViewsData.php b/core/modules/views/src/ViewsData.php index 09522b984f..d8ebe901df 100644 --- a/core/modules/views/src/ViewsData.php +++ b/core/modules/views/src/ViewsData.php @@ -6,6 +6,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Extension\Hook\FunctionInvoker; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManagerInterface; @@ -237,10 +238,9 @@ protected function getData() { 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', new FunctionInvoker(function ($hook_implementation, $module) use (&$data) { + $views_data = $hook_implementation(); // Set the provider key for each base table. foreach ($views_data as &$table) { if (isset($table['table']) && !isset($table['table']['provider'])) { @@ -248,7 +248,8 @@ protected function getData() { } } $data = NestedArray::mergeDeep($data, $views_data); - } + + })); $this->moduleHandler->alter('views_data', $data); $this->processEntityTypes($data); diff --git a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php index 32d043da9e..d3357af1e8 100644 --- a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php @@ -318,14 +318,6 @@ public function testSaveUpdate(EntityInterface $entity) { $this->keyValueStore->expects($this->never()) ->method('delete'); - $this->moduleHandler->expects($this->at(0)) - ->method('getImplementations') - ->with('entity_load') - ->will($this->returnValue([])); - $this->moduleHandler->expects($this->at(1)) - ->method('getImplementations') - ->with('test_entity_type_load') - ->will($this->returnValue([])); $this->moduleHandler->expects($this->at(2)) ->method('invokeAll') ->with('test_entity_type_presave'); @@ -393,14 +385,6 @@ public function testSaveRenameConfigEntity(ConfigEntityInterface $entity) { ->will($this->returnValue(get_class($entity))); $this->setUpKeyValueEntityStorage(); - $this->moduleHandler->expects($this->at(0)) - ->method('getImplementations') - ->with('entity_load') - ->will($this->returnValue([])); - $this->moduleHandler->expects($this->at(1)) - ->method('getImplementations') - ->with('test_entity_type_load') - ->will($this->returnValue([])); $expected = ['id' => 'foo']; $entity->expects($this->once()) ->method('toArray') @@ -518,14 +502,6 @@ public function testLoad() { ->method('getMultiple') ->with(['foo']) ->will($this->returnValue([['id' => 'foo']])); - $this->moduleHandler->expects($this->at(0)) - ->method('getImplementations') - ->with('entity_load') - ->will($this->returnValue([])); - $this->moduleHandler->expects($this->at(1)) - ->method('getImplementations') - ->with('test_entity_type_load') - ->will($this->returnValue([])); $entity = $this->entityStorage->load('foo'); $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity); $this->assertSame('foo', $entity->id()); @@ -543,8 +519,6 @@ public function testLoadMissingEntity() { ->method('getMultiple') ->with(['foo']) ->will($this->returnValue([])); - $this->moduleHandler->expects($this->never()) - ->method('getImplementations'); $entity = $this->entityStorage->load('foo'); $this->assertNull($entity); } @@ -566,14 +540,6 @@ public function testLoadMultipleAll() { $this->keyValueStore->expects($this->once()) ->method('getAll') ->will($this->returnValue([['id' => 'foo'], ['id' => 'bar']])); - $this->moduleHandler->expects($this->at(0)) - ->method('getImplementations') - ->with('entity_load') - ->will($this->returnValue([])); - $this->moduleHandler->expects($this->at(1)) - ->method('getImplementations') - ->with('test_entity_type_load') - ->will($this->returnValue([])); $entities = $this->entityStorage->loadMultiple(); foreach ($entities as $id => $entity) { $this->assertInstanceOf('Drupal\Core\Entity\EntityInterface', $entity); @@ -600,14 +566,6 @@ public function testLoadMultipleIds() { ->method('getMultiple') ->with(['foo']) ->will($this->returnValue([['id' => 'foo']])); - $this->moduleHandler->expects($this->at(0)) - ->method('getImplementations') - ->with('entity_load') - ->will($this->returnValue([])); - $this->moduleHandler->expects($this->at(1)) - ->method('getImplementations') - ->with('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/Theme/RegistryTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php index f2cd22ea90..bc8b2ae6ad 100644 --- a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php +++ b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php @@ -134,10 +134,12 @@ public function testGetRegistryForModule() { // 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/modules/system/tests/themes/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($hook, $invoker) { + $invoker('theme_test', $hook); + }); $this->moduleHandler->expects($this->atLeastOnce()) ->method('getModuleList') ->willReturn([]);