Loading core/lib/Drupal/Core/Extension/ModuleHandler.php +5 −1 Original line number Diff line number Diff line Loading @@ -333,6 +333,9 @@ public function getHookInfo() { */ public function resetImplementations() { $this->alterEventListeners = []; $this->invokeMap = []; $this->listenersByHook = []; $this->modulesByHook = []; } /** Loading Loading @@ -730,6 +733,7 @@ protected function getHookListeners(string $hook): array { */ protected function getFlatHookListeners(string $hook): array { if (!isset($this->listenersByHook[$hook])) { $this->listenersByHook[$hook] = []; foreach ($this->eventDispatcher->getListeners("drupal_hook.$hook") as $listener) { if (is_array($listener) && is_object($listener[0])) { $module = $this->hookImplementationsMap[$hook][get_class($listener[0])][$listener[1]]; Loading @@ -755,7 +759,7 @@ protected function getFlatHookListeners(string $hook): array { } } return $this->listenersByHook[$hook] ?? []; return $this->listenersByHook[$hook]; } } core/modules/system/tests/modules/module_test/module_test.file.inc +14 −0 Original line number Diff line number Diff line Loading @@ -16,3 +16,17 @@ function module_test_test_hook(): array { return ['module_test' => 'success!']; } /** * Implements hook_test_reset_implementations_hook(). */ function module_test_test_reset_implementations_hook(): string { return __FUNCTION__; } /** * Implements hook_test_reset_implementations_alter(). */ function module_test_test_reset_implementations_alter(array &$data): void { $data[] = __FUNCTION__; } core/tests/Drupal/KernelTests/Core/Extension/ModuleHandlerTest.php +75 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ namespace Drupal\KernelTests\Core\Extension; use Drupal\Core\Extension\Exception\UnknownExtensionException; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\KernelTests\KernelTestBase; /** Loading Loading @@ -36,4 +37,78 @@ public function testGetNameDeprecation(): void { $this->assertNotNull(\Drupal::service('module_handler')->getName('module_test')); } /** * Tests that resetImplementations() clears the hook memory cache. * * @covers ::resetImplementations */ public function testResetImplementationsClearsHooks(): void { $oldModuleHandler = \Drupal::moduleHandler(); $this->assertHasResetHookImplementations(FALSE, $oldModuleHandler); // Installing a module does not trigger ->resetImplementations(). /** @var \Drupal\Core\Extension\ModuleInstallerInterface $moduleInstaller */ $moduleInstaller = \Drupal::service('module_installer'); $moduleInstaller->install(['module_test']); $this->assertHasResetHookImplementations(FALSE, $oldModuleHandler); // Only the new ModuleHandler instance has the updated implementations. $moduleHandler = \Drupal::moduleHandler(); $this->assertHasResetHookImplementations(TRUE, $moduleHandler); $backupModuleList = $moduleHandler->getModuleList(); $moduleListWithout = array_diff_key($backupModuleList, ['module_test' => TRUE]); $this->assertArrayHasKey('module_test', $backupModuleList); // Silently setting the property does not clear the hooks cache. $moduleListProperty = (new \ReflectionProperty($moduleHandler, 'moduleList')); $this->assertSame($backupModuleList, $moduleListProperty->getValue($moduleHandler)); $moduleListProperty->setValue($moduleHandler, $moduleListWithout); $this->assertHasResetHookImplementations(TRUE, $moduleHandler); // Directly calling ->resetImplementations() clears the hook caches. $moduleHandler->resetImplementations(); $this->assertHasResetHookImplementations(FALSE, $moduleHandler); $moduleListProperty->setValue($moduleHandler, $backupModuleList); $this->assertHasResetHookImplementations(FALSE, $moduleHandler); $moduleHandler->resetImplementations(); $this->assertHasResetHookImplementations(TRUE, $moduleHandler); // Calling ->setModuleList() triggers ->resetImplementations(). $moduleHandler->setModuleList(['system']); $this->assertHasResetHookImplementations(FALSE, $moduleHandler); $moduleHandler->setModuleList($backupModuleList); $this->assertHasResetHookImplementations(TRUE, $moduleHandler); // Uninstalling a module triggers ->resetImplementations(). /** @var \Drupal\Core\Extension\ModuleInstallerInterface $moduleInstaller */ $moduleInstaller = \Drupal::service('module_installer'); $moduleInstaller->uninstall(['module_test']); $this->assertSame($moduleListWithout, $moduleHandler->getModuleList()); $this->assertHasResetHookImplementations(FALSE, $moduleHandler); } /** * Asserts whether certain hook implementations exist. * * This is used to verify that all internal hook cache properties have been * reset and updated. * * @param bool $exists * TRUE if the implementations are expected to exist, FALSE if not. * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler * The module handler. * * @see \module_test_test_reset_implementations_hook() * @see \module_test_test_reset_implementations_alter() */ protected function assertHasResetHookImplementations(bool $exists, ModuleHandlerInterface $moduleHandler): void { $this->assertSame($exists, $moduleHandler->hasImplementations('test_reset_implementations_hook')); $this->assertSame($exists, $moduleHandler->hasImplementations('test_reset_implementations_alter')); $expected_list = $exists ? ['module_test_test_reset_implementations_hook'] : []; $this->assertSame($expected_list, $moduleHandler->invokeAll('test_reset_implementations_hook')); $expected_alter_list = $exists ? ['module_test_test_reset_implementations_alter'] : []; $alter_list = []; $moduleHandler->alter('test_reset_implementations', $alter_list); $this->assertSame($expected_alter_list, $alter_list); } } Loading
core/lib/Drupal/Core/Extension/ModuleHandler.php +5 −1 Original line number Diff line number Diff line Loading @@ -333,6 +333,9 @@ public function getHookInfo() { */ public function resetImplementations() { $this->alterEventListeners = []; $this->invokeMap = []; $this->listenersByHook = []; $this->modulesByHook = []; } /** Loading Loading @@ -730,6 +733,7 @@ protected function getHookListeners(string $hook): array { */ protected function getFlatHookListeners(string $hook): array { if (!isset($this->listenersByHook[$hook])) { $this->listenersByHook[$hook] = []; foreach ($this->eventDispatcher->getListeners("drupal_hook.$hook") as $listener) { if (is_array($listener) && is_object($listener[0])) { $module = $this->hookImplementationsMap[$hook][get_class($listener[0])][$listener[1]]; Loading @@ -755,7 +759,7 @@ protected function getFlatHookListeners(string $hook): array { } } return $this->listenersByHook[$hook] ?? []; return $this->listenersByHook[$hook]; } }
core/modules/system/tests/modules/module_test/module_test.file.inc +14 −0 Original line number Diff line number Diff line Loading @@ -16,3 +16,17 @@ function module_test_test_hook(): array { return ['module_test' => 'success!']; } /** * Implements hook_test_reset_implementations_hook(). */ function module_test_test_reset_implementations_hook(): string { return __FUNCTION__; } /** * Implements hook_test_reset_implementations_alter(). */ function module_test_test_reset_implementations_alter(array &$data): void { $data[] = __FUNCTION__; }
core/tests/Drupal/KernelTests/Core/Extension/ModuleHandlerTest.php +75 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ namespace Drupal\KernelTests\Core\Extension; use Drupal\Core\Extension\Exception\UnknownExtensionException; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\KernelTests\KernelTestBase; /** Loading Loading @@ -36,4 +37,78 @@ public function testGetNameDeprecation(): void { $this->assertNotNull(\Drupal::service('module_handler')->getName('module_test')); } /** * Tests that resetImplementations() clears the hook memory cache. * * @covers ::resetImplementations */ public function testResetImplementationsClearsHooks(): void { $oldModuleHandler = \Drupal::moduleHandler(); $this->assertHasResetHookImplementations(FALSE, $oldModuleHandler); // Installing a module does not trigger ->resetImplementations(). /** @var \Drupal\Core\Extension\ModuleInstallerInterface $moduleInstaller */ $moduleInstaller = \Drupal::service('module_installer'); $moduleInstaller->install(['module_test']); $this->assertHasResetHookImplementations(FALSE, $oldModuleHandler); // Only the new ModuleHandler instance has the updated implementations. $moduleHandler = \Drupal::moduleHandler(); $this->assertHasResetHookImplementations(TRUE, $moduleHandler); $backupModuleList = $moduleHandler->getModuleList(); $moduleListWithout = array_diff_key($backupModuleList, ['module_test' => TRUE]); $this->assertArrayHasKey('module_test', $backupModuleList); // Silently setting the property does not clear the hooks cache. $moduleListProperty = (new \ReflectionProperty($moduleHandler, 'moduleList')); $this->assertSame($backupModuleList, $moduleListProperty->getValue($moduleHandler)); $moduleListProperty->setValue($moduleHandler, $moduleListWithout); $this->assertHasResetHookImplementations(TRUE, $moduleHandler); // Directly calling ->resetImplementations() clears the hook caches. $moduleHandler->resetImplementations(); $this->assertHasResetHookImplementations(FALSE, $moduleHandler); $moduleListProperty->setValue($moduleHandler, $backupModuleList); $this->assertHasResetHookImplementations(FALSE, $moduleHandler); $moduleHandler->resetImplementations(); $this->assertHasResetHookImplementations(TRUE, $moduleHandler); // Calling ->setModuleList() triggers ->resetImplementations(). $moduleHandler->setModuleList(['system']); $this->assertHasResetHookImplementations(FALSE, $moduleHandler); $moduleHandler->setModuleList($backupModuleList); $this->assertHasResetHookImplementations(TRUE, $moduleHandler); // Uninstalling a module triggers ->resetImplementations(). /** @var \Drupal\Core\Extension\ModuleInstallerInterface $moduleInstaller */ $moduleInstaller = \Drupal::service('module_installer'); $moduleInstaller->uninstall(['module_test']); $this->assertSame($moduleListWithout, $moduleHandler->getModuleList()); $this->assertHasResetHookImplementations(FALSE, $moduleHandler); } /** * Asserts whether certain hook implementations exist. * * This is used to verify that all internal hook cache properties have been * reset and updated. * * @param bool $exists * TRUE if the implementations are expected to exist, FALSE if not. * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler * The module handler. * * @see \module_test_test_reset_implementations_hook() * @see \module_test_test_reset_implementations_alter() */ protected function assertHasResetHookImplementations(bool $exists, ModuleHandlerInterface $moduleHandler): void { $this->assertSame($exists, $moduleHandler->hasImplementations('test_reset_implementations_hook')); $this->assertSame($exists, $moduleHandler->hasImplementations('test_reset_implementations_alter')); $expected_list = $exists ? ['module_test_test_reset_implementations_hook'] : []; $this->assertSame($expected_list, $moduleHandler->invokeAll('test_reset_implementations_hook')); $expected_alter_list = $exists ? ['module_test_test_reset_implementations_alter'] : []; $alter_list = []; $moduleHandler->alter('test_reset_implementations', $alter_list); $this->assertSame($expected_alter_list, $alter_list); } }