diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php (revision bd8f2172b3d2e3cfc40d714d2a89cfccfc0c73be) +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php (revision 23cc4a5cd6cf0056a4875edafa56d8543f856013) @@ -623,6 +623,12 @@ protected function buildImplementationInfo($hook) { $implementations = []; $hook_info = $this->getHookInfo(); + + // In case it's an early stage and the hook info was loaded from cache, the + // module files are not yet loaded at this point and discovery below + // produces an empty list. + $this->loadAll(); + foreach ($this->moduleList as $module => $extension) { $include_file = isset($hook_info[$hook]['group']) && $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); // Since $this->implementsHook() may needlessly try to load the include @@ -670,6 +676,17 @@ * from the cache. */ protected function verifyImplementations(&$implementations, $hook) { + // Under certain circumstances the module files are not yet loaded. This + // happens for example when invoking a hook inside the constructor of a + // http_middleware service; these services are constructed very early as + // a dependency of http_kernel service. A more concrete example is a + // middleware service using the entity_type.manager. Most of the times + // the entity type information is retrieved from cache (stored in the + // discovery cache bin). When this cache however is missing, hooks + // like hook_entity_type_build() and hook_entity_type_alter() need to be + // invoked at this early stage. + $this->loadAll(); + $all_valid = TRUE; foreach ($implementations as $module => $group) { // If this hook implementation is stored in a lazy-loaded file, include diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php --- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php (revision 23cc4a5cd6cf0056a4875edafa56d8543f856013) +++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php (revision cb22a9ddd07a3febabccc2ce03926383b4f1792f) @@ -369,6 +369,7 @@ * * @covers ::getImplementations * @covers ::getImplementationInfo + * @covers ::verifyImplementations */ public function testCachedGetImplementationsMissingMethod() { $this->cacheBackend->expects($this->exactly(1)) @@ -395,7 +396,6 @@ ]) ->onlyMethods(['buildImplementationInfo']) ->getMock(); - $module_handler->load('module_handler_test'); $module_handler->expects($this->never())->method('buildImplementationInfo'); $this->assertEquals(['module_handler_test'], $module_handler->getImplementations('hook')); @@ -504,4 +504,27 @@ $this->assertEquals(['node' => $this->root . '/core/modules/node'], $module_handler->getModuleDirectories()); } + /** + * Tests that modules are included in case of a partial cache miss. + * + * @covers ::getImplementations + * @covers ::getImplementationInfo + * @covers ::buildImplementationInfo + */ + public function testMissingHookImplementationCache() { + // Simulate missing cache entry for hook implementations, but existing one + // for hook info. + $this->cacheBackend + ->expects($this->exactly(2)) + ->method('get') + ->willReturnMap([ + ['hook_info', FALSE, (object) ['data' => []]], + ['module_implements', FALSE, FALSE], + ]); + + $module_handler = $this->getModuleHandler(); + $implementations = $module_handler->getImplementations('hook'); + $this->assertEquals(['module_handler_test'], $implementations); + } + }