Loading core/lib/Drupal/Core/Extension/ModuleHandler.php +14 −1 Changes for core/lib/Drupal/Core/Extension/ModuleHandler.php: 14 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -296,7 +296,20 @@ public function invoke($module, $hook, array $args = []) { $listeners = $list->getForModule($module); if ($listeners) { if (count($listeners) > 1) { throw new \LogicException("Module $module should not implement $hook more than once"); $results = []; foreach ($listeners as $listener) { $result = $listener(...$args); if ($result === NULL) { continue; } if (!is_array($result) && !($result instanceof \ArrayObject)) { // If the result is not an array and there is more than one // implementation for the module we cannot merge the results. throw new \LogicException("Module $module should not implement $hook more than once."); } $results = NestedArray::mergeDeep($results, $result); } return $results; } return reset($listeners)(... $args); } Loading core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +3 −0 Changes for core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -185,6 +185,9 @@ public function invokeAllWith(string $hook, callable $callback): void; /** * Invokes a hook in a particular module. * * This will execute multiple implementations as long as all returned values * are void or an array. Arrays will be merged with NestedArray::mergeDeep. * * @param string $module * The name of the module (without the .module extension). * @param string $hook Loading core/modules/system/tests/modules/hook_single_invoke/hook_single_invoke.info.yml 0 → 100644 +7 −0 Changes for core/modules/system/tests/modules/hook_single_invoke/hook_single_invoke.info.yml: 7 added lines, 0 removed lines. Original line number Diff line number Diff line name: Hook test single module invokes type: module description: 'Test module used to test module handler invoke.' package: Testing version: VERSION core_version_requirement: '*' hidden: true core/modules/system/tests/modules/hook_single_invoke/src/Hook/TestHookInvoke.php 0 → 100644 +84 −0 Changes for core/modules/system/tests/modules/hook_single_invoke/src/Hook/TestHookInvoke.php: 84 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\hook_single_invoke\Hook; use Drupal\Core\Hook\Attribute\Hook; /** * Add four hook implementations for two hooks, one returns arrays one string. */ class TestHookInvoke { /** * This hook returns an array. */ #[Hook('custom_hook_invoke_array')] public function hookInvokeSingleArrayOne(): array { return [__METHOD__]; } /** * This hook returns an array. */ #[Hook('custom_hook_invoke_array')] public function hookInvokeSingleArrayTwo(): array { return [__METHOD__]; } /** * This hook returns an ArrayObject. * * @return \ArrayObject< int, string > * ArrayObject of the method. */ #[Hook('custom_hook_invoke_array_object')] public function hookInvokeSingleArrayObjectOne(): \ArrayObject { return new \ArrayObject([__METHOD__]); } /** * This hook returns an ArrayObject. * * @return \ArrayObject< int, string > * ArrayObject of the method. */ #[Hook('custom_hook_invoke_array_object')] public function hookInvokeSingleArrayObjectTwo(): \ArrayObject { return new \ArrayObject([__METHOD__]); } /** * This hook returns a string. */ #[Hook('custom_hook_invoke_string')] public function hookInvokeSingleStringOne(): string { return __METHOD__; } /** * This hook returns a string. */ #[Hook('custom_hook_invoke_string')] public function hookInvokeSingleStringTwo(): string { return __METHOD__; } /** * This hook returns a stdClass. */ #[Hook('custom_hook_invoke_class')] public function hookInvokeSingleClassOne(): \stdClass { return new \stdClass(); } /** * This hook returns a stdClass. */ #[Hook('custom_hook_invoke_class')] public function hookInvokeSingleClassTwo(): \stdClass { return new \stdClass(); } } core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php +43 −0 Changes for core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php: 43 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -397,6 +397,49 @@ public function testOopPreprocess(): void { } } /** * Tests invoke works for multiple implementations that can be merged. */ public function testInvokeWithMergeable(): void { $this->moduleInstaller()->install(['hook_single_invoke']); $expected = [ 'Drupal\hook_single_invoke\Hook\TestHookInvoke::hookInvokeSingleArrayOne', 'Drupal\hook_single_invoke\Hook\TestHookInvoke::hookInvokeSingleArrayTwo', ]; $this->assertEquals($expected, $this->moduleHandler()->invoke('hook_single_invoke', 'custom_hook_invoke_array', args: [TRUE])); $expected = [ 'Drupal\hook_single_invoke\Hook\TestHookInvoke::hookInvokeSingleArrayObjectOne', 'Drupal\hook_single_invoke\Hook\TestHookInvoke::hookInvokeSingleArrayObjectTwo', ]; $this->assertEquals($expected, $this->moduleHandler()->invoke('hook_single_invoke', 'custom_hook_invoke_array_object', args: [TRUE])); } /** * Tests invoke fails for multiple implementations that cannot be merged. */ public function testInvokeWithNotMergeableString(): void { $this->moduleInstaller()->install(['hook_single_invoke']); $expected_exception_message = 'Module hook_single_invoke should not implement custom_hook_invoke_string more than once.'; $this->expectException(\LogicException::class); $this->expectExceptionMessage($expected_exception_message); $this->moduleHandler()->invoke('hook_single_invoke', 'custom_hook_invoke_string', args: [TRUE]); } /** * Tests invoke fails for multiple implementations that cannot be merged. */ public function testInvokeWithNotMergeableClass(): void { $this->moduleInstaller()->install(['hook_single_invoke']); $expected_exception_message = 'Module hook_single_invoke should not implement custom_hook_invoke_class more than once.'; $this->expectException(\LogicException::class); $this->expectExceptionMessage($expected_exception_message); $this->moduleHandler()->invoke('hook_single_invoke', 'custom_hook_invoke_class', args: [TRUE]); } /** * Returns the ModuleHandler. * Loading Loading
core/lib/Drupal/Core/Extension/ModuleHandler.php +14 −1 Changes for core/lib/Drupal/Core/Extension/ModuleHandler.php: 14 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -296,7 +296,20 @@ public function invoke($module, $hook, array $args = []) { $listeners = $list->getForModule($module); if ($listeners) { if (count($listeners) > 1) { throw new \LogicException("Module $module should not implement $hook more than once"); $results = []; foreach ($listeners as $listener) { $result = $listener(...$args); if ($result === NULL) { continue; } if (!is_array($result) && !($result instanceof \ArrayObject)) { // If the result is not an array and there is more than one // implementation for the module we cannot merge the results. throw new \LogicException("Module $module should not implement $hook more than once."); } $results = NestedArray::mergeDeep($results, $result); } return $results; } return reset($listeners)(... $args); } Loading
core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +3 −0 Changes for core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -185,6 +185,9 @@ public function invokeAllWith(string $hook, callable $callback): void; /** * Invokes a hook in a particular module. * * This will execute multiple implementations as long as all returned values * are void or an array. Arrays will be merged with NestedArray::mergeDeep. * * @param string $module * The name of the module (without the .module extension). * @param string $hook Loading
core/modules/system/tests/modules/hook_single_invoke/hook_single_invoke.info.yml 0 → 100644 +7 −0 Changes for core/modules/system/tests/modules/hook_single_invoke/hook_single_invoke.info.yml: 7 added lines, 0 removed lines. Original line number Diff line number Diff line name: Hook test single module invokes type: module description: 'Test module used to test module handler invoke.' package: Testing version: VERSION core_version_requirement: '*' hidden: true
core/modules/system/tests/modules/hook_single_invoke/src/Hook/TestHookInvoke.php 0 → 100644 +84 −0 Changes for core/modules/system/tests/modules/hook_single_invoke/src/Hook/TestHookInvoke.php: 84 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\hook_single_invoke\Hook; use Drupal\Core\Hook\Attribute\Hook; /** * Add four hook implementations for two hooks, one returns arrays one string. */ class TestHookInvoke { /** * This hook returns an array. */ #[Hook('custom_hook_invoke_array')] public function hookInvokeSingleArrayOne(): array { return [__METHOD__]; } /** * This hook returns an array. */ #[Hook('custom_hook_invoke_array')] public function hookInvokeSingleArrayTwo(): array { return [__METHOD__]; } /** * This hook returns an ArrayObject. * * @return \ArrayObject< int, string > * ArrayObject of the method. */ #[Hook('custom_hook_invoke_array_object')] public function hookInvokeSingleArrayObjectOne(): \ArrayObject { return new \ArrayObject([__METHOD__]); } /** * This hook returns an ArrayObject. * * @return \ArrayObject< int, string > * ArrayObject of the method. */ #[Hook('custom_hook_invoke_array_object')] public function hookInvokeSingleArrayObjectTwo(): \ArrayObject { return new \ArrayObject([__METHOD__]); } /** * This hook returns a string. */ #[Hook('custom_hook_invoke_string')] public function hookInvokeSingleStringOne(): string { return __METHOD__; } /** * This hook returns a string. */ #[Hook('custom_hook_invoke_string')] public function hookInvokeSingleStringTwo(): string { return __METHOD__; } /** * This hook returns a stdClass. */ #[Hook('custom_hook_invoke_class')] public function hookInvokeSingleClassOne(): \stdClass { return new \stdClass(); } /** * This hook returns a stdClass. */ #[Hook('custom_hook_invoke_class')] public function hookInvokeSingleClassTwo(): \stdClass { return new \stdClass(); } }
core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php +43 −0 Changes for core/modules/system/tests/src/Kernel/Extension/ModuleHandlerTest.php: 43 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -397,6 +397,49 @@ public function testOopPreprocess(): void { } } /** * Tests invoke works for multiple implementations that can be merged. */ public function testInvokeWithMergeable(): void { $this->moduleInstaller()->install(['hook_single_invoke']); $expected = [ 'Drupal\hook_single_invoke\Hook\TestHookInvoke::hookInvokeSingleArrayOne', 'Drupal\hook_single_invoke\Hook\TestHookInvoke::hookInvokeSingleArrayTwo', ]; $this->assertEquals($expected, $this->moduleHandler()->invoke('hook_single_invoke', 'custom_hook_invoke_array', args: [TRUE])); $expected = [ 'Drupal\hook_single_invoke\Hook\TestHookInvoke::hookInvokeSingleArrayObjectOne', 'Drupal\hook_single_invoke\Hook\TestHookInvoke::hookInvokeSingleArrayObjectTwo', ]; $this->assertEquals($expected, $this->moduleHandler()->invoke('hook_single_invoke', 'custom_hook_invoke_array_object', args: [TRUE])); } /** * Tests invoke fails for multiple implementations that cannot be merged. */ public function testInvokeWithNotMergeableString(): void { $this->moduleInstaller()->install(['hook_single_invoke']); $expected_exception_message = 'Module hook_single_invoke should not implement custom_hook_invoke_string more than once.'; $this->expectException(\LogicException::class); $this->expectExceptionMessage($expected_exception_message); $this->moduleHandler()->invoke('hook_single_invoke', 'custom_hook_invoke_string', args: [TRUE]); } /** * Tests invoke fails for multiple implementations that cannot be merged. */ public function testInvokeWithNotMergeableClass(): void { $this->moduleInstaller()->install(['hook_single_invoke']); $expected_exception_message = 'Module hook_single_invoke should not implement custom_hook_invoke_class more than once.'; $this->expectException(\LogicException::class); $this->expectExceptionMessage($expected_exception_message); $this->moduleHandler()->invoke('hook_single_invoke', 'custom_hook_invoke_class', args: [TRUE]); } /** * Returns the ModuleHandler. * Loading