Commit 46820e24 authored by catch's avatar catch
Browse files

Issue #3514197 by donquixote, acbramley, nicxvan, godotislate, longwave:...

Issue #3514197 by donquixote, acbramley, nicxvan, godotislate, longwave: ModuleHandler::resetImplementations should reset all properties with hook implementations
parent 98ab640e
Loading
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -333,6 +333,9 @@ public function getHookInfo() {
   */
  public function resetImplementations() {
    $this->alterEventListeners = [];
    $this->invokeMap = [];
    $this->listenersByHook = [];
    $this->modulesByHook = [];
  }

  /**
@@ -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]];
@@ -755,7 +759,7 @@ protected function getFlatHookListeners(string $hook): array {
      }
    }

    return $this->listenersByHook[$hook] ?? [];
    return $this->listenersByHook[$hook];
  }

}
+14 −0
Original line number Diff line number Diff line
@@ -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__;
}
+75 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
namespace Drupal\KernelTests\Core\Extension;

use Drupal\Core\Extension\Exception\UnknownExtensionException;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\KernelTests\KernelTestBase;

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

}