diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 423262d..8458c7c 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -5,8 +5,6 @@ use Drupal\Core\DrupalKernel; use Drupal\Core\Database\Database; use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\ModuleHandler; -use Drupal\Core\InstallModuleHandler; use Symfony\Component\ClassLoader\UniversalClassLoader; use Symfony\Component\ClassLoader\ApcUniversalClassLoader; use Symfony\Component\DependencyInjection\Container; diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index edaf2a7..1ba401c 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -348,9 +348,9 @@ function install_begin_request(&$install_state) { // implementation here. $container->register('lock', 'Drupal\Core\Lock\NullLockBackend'); - // Register an extension handler for managing enabled modules. + // Register a module handler for managing enabled modules. $container - ->register('module_handler', 'Drupal\Core\InstallModuleHandler'); + ->register('module_handler', 'Drupal\Core\ModuleHandler'); drupal_container($container); } @@ -359,9 +359,12 @@ function install_begin_request(&$install_state) { require_once DRUPAL_ROOT . '/core/includes/ajax.inc'; - // Override the module list with a minimal set of modules. - drupal_container()->get('module_handler')->setModuleList(array('system' => 'core/modules/system/system.module')); - drupal_container()->get('module_handler')->load('system'); + $module_handler = drupal_container()->get('module_handler'); + if (!$module_handler->moduleExists('system')) { + // Override the module list with a minimal set of modules. + $module_handler->setModuleList(array('system' => 'core/modules/system/system.module')); + } + $module_handler->load('system'); require_once DRUPAL_ROOT . '/core/includes/cache.inc'; $conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend'); diff --git a/core/includes/install.inc b/core/includes/install.inc index 7067934..c6b3c50 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -426,6 +426,7 @@ function drupal_install_system() { // Reset the module list. drupal_container()->get('module_handler')->setModuleList(array('system' => $system_path . '/system.module')); + drupal_container()->get('module_handler')->resetImplementations(); config_install_default_config('module', 'system'); diff --git a/core/lib/Drupal/Core/CachedModuleHandler.php b/core/lib/Drupal/Core/CachedModuleHandler.php new file mode 100644 index 0000000..46e0ea5 --- /dev/null +++ b/core/lib/Drupal/Core/CachedModuleHandler.php @@ -0,0 +1,177 @@ +moduleList = $module_list; + $this->state = $state; + $this->bootstrapCache = $bootstrap_cache; + } + + /** + * Implements \Drupal\Core\ModuleHandlerInterface::getBootstrapModules(). + */ + public function getBootstrapModules() { + if (isset($this->bootstrapModules)) { + return $this->bootstrapModules; + } + if ($cached = $this->bootstrapCache->get('bootstrap_modules')) { + $bootstrap_list = $cached->data; + } + else { + $bootstrap_list = $this->state->get('system.module.bootstrap') ?: array(); + $this->bootstrapCache->set('bootstrap_modules', $bootstrap_list); + } + $this->bootstrapModules = array_keys($bootstrap_list); + return $this->bootstrapModules; + } + + /** + * Implements \Drupal\Core\ModuleHandlerInterface::resetImplementations(). + */ + public function resetImplementations() { + // We maintain a persistent cache of hook implementations in addition to the + // static cache to avoid looping through every module and every hook on each + // request. Benchmarks show that the benefit of this caching outweighs the + // additional database hit even when using the default database caching + // backend and only a small number of modules are enabled. The cost of the + // $this->bootstrapCache->get() is more or less constant and reduced further when + // non-database caching backends are used, so there will be more significant + // gains when a large number of modules are installed or hooks invoked, since + // this can quickly lead to module_hook() being called several thousand times + // per request. + parent::resetImplementations(); + $this->bootstrapCache->set('module_implements', array()); + $this->bootstrapCache->delete('hook_info'); + } + + /** + * Implements \Drupal\Core\CachedModuleHandlerInterface::writeCache(). + */ + public function writeCache() { + if ($this->cacheNeedsWriting) { + $this->bootstrapCache->set('module_implements', $this->implementations); + $this->cacheNeedsWriting = FALSE; + } + } + + /** + * Overrides \Drupal\Core\ModuleHandler::getImplementationInfo(). + */ + protected function getImplementationInfo($hook) { + $implementations = $this->getCachedImplementations(); + if (!isset($implementations[$hook])) { + // The hook is not cached, so ensure that whether or not it has + // implementations, the cache is updated at the end of the request. + $this->cacheNeedsWriting = TRUE; + $implementations[$hook] = parent::getImplementationInfo($hook); + } + else { + foreach ($implementations[$hook] as $module => $group) { + // If this hook implementation is stored in a lazy-loaded file, include + // that file first. + if ($group) { + $this->loadInclude($module, 'inc', "$module.$group"); + } + // It is possible that a module removed a hook implementation without the + // implementations cache being rebuilt yet, so we check whether the + // function exists on each request to avoid undefined function errors. + // Since module_hook() may needlessly try to load the include file again, + // function_exists() is used directly here. + if (!function_exists($module . '_' . $hook)) { + // Clear out the stale implementation from the cache and force a cache + // refresh to forget about no longer existing hook implementations. + unset($implementations[$hook][$module]); + $this->cacheNeedsWriting = TRUE; + } + } + } + $this->implementations = $implementations; + return $implementations[$hook]; + } + + /** + * Overrides \Drupal\Core\ModuleHandler::getHookInfo(). + */ + protected function getHookInfo() { + // When this function is indirectly invoked from bootstrap_invoke_all() prior + // to all modules being loaded, we do not want to cache an incomplete + // hook_hookInfo() result, so instead return an empty array. This requires + // bootstrap hook implementations to reside in the .module file, which is + // optimal for performance anyway. + if (!$this->loaded) { + return array(); + } + // Optimize for fast access to definitions if they are already in memory. + if (isset($this->hookInfo)) { + return $this->hookInfo; + } + + $this->hookInfo = array(); + $cache = $this->bootstrapCache->get('hook_info'); + if ($cache === FALSE) { + $this->hookInfo = parent::getHookInfo(); + $this->bootstrapCache->set('hook_info', $this->hookInfo); + } + else { + $this->hookInfo = $cache->data; + } + return $this->hookInfo; + } + + /** + * Retrieves hook implementation info from the cache. + */ + protected function getCachedImplementations() { + if (isset($this->implementations)) { + return $this->implementations; + } + + $implementations = $this->bootstrapCache->get('module_implements'); + if ($implementations === FALSE) { + $implementations = array(); + } + else { + $implementations = $implementations->data; + } + $this->implementations = $implementations; + return $implementations; + } +} diff --git a/core/lib/Drupal/Core/CachedModuleHandlerInterface.php b/core/lib/Drupal/Core/CachedModuleHandlerInterface.php new file mode 100644 index 0000000..4eabc8b --- /dev/null +++ b/core/lib/Drupal/Core/CachedModuleHandlerInterface.php @@ -0,0 +1,20 @@ +setFactoryMethod('get') ->addArgument('bootstrap'); - // The ModuleHandler manages enabled modules and provides the ability to - // invoke hooks in all enabled modules. - $container->register('module_handler', 'Drupal\Core\ModuleHandler') - ->addArgument('%container.modules%') - ->addArgument(new Reference('state')) - ->addArgument(new Reference('cache.bootstrap')); + $this->registerModuleHandler($container); $container->register('http_kernel', 'Drupal\Core\HttpKernel') ->addArgument(new Reference('event_dispatcher')) @@ -276,6 +271,25 @@ public function build(ContainerBuilder $container) { } /** + * Registers the module handler. + */ + protected function registerModuleHandler(ContainerBuilder $container) { + // The ModuleHandler manages enabled modules and provides the ability to + // invoke hooks in all enabled modules. + if ($container->getParameter('kernel.environment') == 'install') { + // During installation we use the non-cached version. + $container->register('module_handler', 'Drupal\Core\ModuleHandler') + ->addArgument('%container.modules%'); + } + else { + $container->register('module_handler', 'Drupal\Core\CachedModuleHandler') + ->addArgument('%container.modules%') + ->addArgument(new Reference('state')) + ->addArgument(new Reference('cache.bootstrap')); + } + } + + /** * Registers Twig services. */ protected function registerTwig(ContainerBuilder $container) { diff --git a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php index 0e9ee9b..2358a09 100644 --- a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php @@ -41,14 +41,14 @@ function __construct(ModuleHandlerInterface $module_handler) { * The Event to process. */ public function onTerminate(PostResponseEvent $event) { - module_invoke_all('exit'); + $this->ModuleHandler->invokeAll('exit'); $request_method = $event->getRequest()->getMethod(); // Check whether we need to write the module implementations cache. We do // not want to cache hooks which are only invoked on HTTP POST requests // since these do not need to be optimized as tightly, and not doing so // keeps the cache entry smaller. - if ($request_method == 'GET' || $request_method == 'HEAD') { - $this->ModuleHandler->writeImplementationsCache(); + if (($request_method == 'GET' || $request_method == 'HEAD') && is_callable(array($this->ModuleHandler, 'writeCache'))) { + $this->ModuleHandler->writeCache(); } system_run_automated_cron(); } diff --git a/core/lib/Drupal/Core/InstallModuleHandler.php b/core/lib/Drupal/Core/InstallModuleHandler.php deleted file mode 100644 index 86223df..0000000 --- a/core/lib/Drupal/Core/InstallModuleHandler.php +++ /dev/null @@ -1,141 +0,0 @@ -moduleList = $module_list; - } - - /** - * Overrides Drupal\Core\ModuleHandler::getImplementations(). - */ - public function getImplementations($hook) { - // Fetch implementations from cache. - if (empty($this->implementations)) { - $this->implementations = array(); - } - if (!isset($this->implementations[$hook])) { - // The hook is not cached, so ensure that whether or not it has - // implementations, that the cache is updated at the end of the request. - $this->implementations['#write_cache'] = TRUE; - $hook_info = $this->getHookInfo(); - $this->implementations[$hook] = array(); - foreach ($this->moduleList as $module => $filename) { - $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); - // Since module_hook() may needlessly try to load the include file again, - // function_exists() is used directly here. - if (function_exists($module . '_' . $hook)) { - $this->implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; - } - } - // Allow modules to change the weight of specific implementations but avoid - // an infinite loop. - if ($hook != 'module_implements_alter') { - $this->alter('module_implements', $this->implementations[$hook], $hook); - } - } - else { - foreach ($this->implementations[$hook] as $module => $group) { - // If this hook implementation is stored in a lazy-loaded file, so include - // that file first. - if ($group) { - module_load_include('inc', $module, "$module.$group"); - } - // It is possible that a module removed a hook implementation without the - // implementations cache being rebuilt yet, so we check whether the - // function exists on each request to avoid undefined function errors. - // Since $this->hookImplements() may needlessly try to load the include - // file again, function_exists() is used directly here. - if (!function_exists($module . '_' . $hook)) { - // Clear out the stale implementation from the cache and force a cache - // refresh to forget about no longer existing hook implementations. - unset($this->implementations[$hook][$module]); - $this->implementations['#write_cache'] = TRUE; - } - } - } - - return array_keys($this->implementations[$hook]); - } - - /** - * Overrides Drupal\Core\ModuleHandler::resetImplementations(). - */ - public function resetImplementations() { - // We maintain a persistent cache of hook implementations in addition to the - // static cache to avoid looping through every module and every hook on each - // request. Benchmarks show that the benefit of this caching outweighs the - // additional database hit even when using the default database caching - // backend and only a small number of modules are enabled. The cost of the - // cache('bootstrap')->get() is more or less constant and reduced further when - // non-database caching backends are used, so there will be more significant - // gains when a large number of modules are installed or hooks invoked, since - // this can quickly lead to module_hook() being called several thousand times - // per request. - $this->implementations = NULL; - $this->hookInfo = NULL; - $this->alterFunctions = NULL; - } - - /** - * Overrides Drupal\Core\ModuleHandler::getHookInfo(). - */ - public function getHookInfo() { - // When this function is indirectly invoked from bootstrap_invoke_all() prior - // to all modules being loaded, we do not want to cache an incomplete - // hook_hook_info() result, so instead return an empty array. This requires - // bootstrap hook implementations to reside in the .module file, which is - // optimal for performance anyway. - if (!$this->loaded) { - return array(); - } - - if (!isset($this->hookInfo)) { - $this->hookInfo = array(); - // We can't use $this->invokeAll() here or it would cause an infinite - // loop. - foreach ($this->moduleList as $module => $filename) { - $function = $module . '_hook_info'; - if (function_exists($function)) { - $result = $function(); - if (isset($result) && is_array($result)) { - $this->hookInfo = array_merge_recursive($this->hookInfo, $result); - } - } - } - // We can't use $this->alter() for the same reason as above. - foreach ($this->moduleList as $module => $filename) { - $function = $module . '_hook_info_alter'; - if (function_exists($function)) { - $function($this->hookInfo); - } - } - } - return $this->hookInfo; - } - - /** - * Overrides Drupal\Core\ModuleHandler::getImplementationsWriteCache(). - */ - public function writeImplementationsCache() { - } -} diff --git a/core/lib/Drupal/Core/ModuleHandler.php b/core/lib/Drupal/Core/ModuleHandler.php index 0cfef0c..698aa7e 100644 --- a/core/lib/Drupal/Core/ModuleHandler.php +++ b/core/lib/Drupal/Core/ModuleHandler.php @@ -19,20 +19,6 @@ class ModuleHandler implements ModuleHandlerInterface { /** - * State key/value store. - * - * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface - */ - protected $state; - - /** - * Cache backend for storing enabled modules. - * - * @var \Drupal\Core\Cache\CacheBackendInterface - */ - protected $bootstrapCache; - - /** * Keeps track internally of loaded files. */ protected $loadedFiles; @@ -70,10 +56,8 @@ class ModuleHandler implements ModuleHandlerInterface { /** * Constructs a ModuleHandler object. */ - public function __construct(array $module_list, KeyValueStoreInterface $state, CacheBackendInterface $bootstrap_cache) { + public function __construct(array $module_list = array()) { $this->moduleList = $module_list; - $this->state = $state; - $this->bootstrapCache = $bootstrap_cache; } /** @@ -149,19 +133,9 @@ public function setModuleList(array $module_list = array()) { * Implements \Drupal\Core\ModuleHandlerInterface::getBootstrapModules(). */ public function getBootstrapModules() { - if (isset($this->bootstrapModules)) { - return $this->bootstrapModules; - } - if ($cached = $this->bootstrapCache->get('bootstrap_modules')) { - $bootstrap_list = $cached->data; - } - else { - $bootstrap_list = $this->state->get('system.module.bootstrap') ?: array(); - $this->bootstrapCache->set('bootstrap_modules', $bootstrap_list); - } - - $this->bootstrapModules = array_keys($bootstrap_list); - return $this->bootstrapModules; + // The basic module handler does not know anything about how to retrieve a + // list of bootstrap modules. + return array(); } /** @@ -226,79 +200,17 @@ public function loadInclude($module, $type, $name = NULL) { * Implements \Drupal\Core\ModuleHandlerInterface::getImplementations(). */ public function getImplementations($hook) { - // Fetch implementations from cache. - if (empty($this->implementations)) { - $implementations = $this->bootstrapCache->get('module_implements'); - if ($implementations === FALSE) { - $this->implementations = array(); - } - else { - $this->implementations = $implementations->data; - } - } - - if (!isset($this->implementations[$hook])) { - // The hook is not cached, so ensure that whether or not it has - // implementations, that the cache is updated at the end of the request. - $this->implementations['#write_cache'] = TRUE; - $hook_info = $this->getHookInfo(); - $this->implementations[$hook] = array(); - foreach ($this->moduleList as $module => $filename) { - $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); - // Since $this->hookImplements() may needlessly try to load the include - // file again, function_exists() is used directly here. - if (function_exists($module . '_' . $hook)) { - $this->implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; - } - } - // Allow modules to change the weight of specific implementations but avoid - // an infinite loop. - if ($hook != 'module_implements_alter') { - $this->alter('module_implements', $this->implementations[$hook], $hook); - } - } - else { - foreach ($this->implementations[$hook] as $module => $group) { - // If this hook implementation is stored in a lazy-loaded file, so include - // that file first. - if ($group) { - module_load_include('inc', $module, "$module.$group"); - } - // It is possible that a module removed a hook implementation without the - // implementations cache being rebuilt yet, so we check whether the - // function exists on each request to avoid undefined function errors. - // Since module_hook() may needlessly try to load the include file again, - // function_exists() is used directly here. - if (!function_exists($module . '_' . $hook)) { - // Clear out the stale implementation from the cache and force a cache - // refresh to forget about no longer existing hook implementations. - unset($this->implementations[$hook][$module]); - $this->implementations['#write_cache'] = TRUE; - } - } - } - return array_keys($this->implementations[$hook]); + $implementations = $this->getImplementationInfo($hook); + return array_keys($implementations); } /** * Implements \Drupal\Core\ModuleHandlerInterface::resetImplementations(). */ public function resetImplementations() { - // We maintain a persistent cache of hook implementations in addition to the - // static cache to avoid looping through every module and every hook on each - // request. Benchmarks show that the benefit of this caching outweighs the - // additional database hit even when using the default database caching - // backend and only a small number of modules are enabled. The cost of the - // $this->bootstrapCache->get() is more or less constant and reduced further when - // non-database caching backends are used, so there will be more significant - // gains when a large number of modules are installed or hooks invoked, since - // this can quickly lead to module_hook() being called several thousand times - // per request. $this->implementations = NULL; - $this->bootstrapCache->set('module_implements', array()); $this->hookInfo = NULL; $this->alterFunctions = NULL; - $this->bootstrapCache->delete('hook_info'); } /** @@ -313,7 +225,7 @@ public function implementsHook($module, $hook) { // optional include file registered via hook_hook_info(). $hook_info = $this->getHookInfo(); if (isset($hook_info[$hook]['group'])) { - module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); + $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); if (function_exists($function)) { return TRUE; } @@ -322,21 +234,12 @@ public function implementsHook($module, $hook) { } /** - * Implements \Drupal\Core\ModuleHandlerInterface::getImplementationsWriteCache(). - */ - public function writeImplementationsCache() { - if (isset($this->implementations['#write_cache'])) { - unset($this->implementations['#write_cache']); - $this->bootstrapCache->set('module_implements', $this->implementations); - } - } - - /** * Implements \Drupal\Core\ModuleHandlerInterface::invokeAll(). */ - public function invokeAll($hook, $args) { + public function invokeAll($hook, $args = array()) { $return = array(); - foreach ($this->getImplementations($hook) as $module) { + $implementations = $this->getImplementations($hook); + foreach ($implementations as $module) { $function = $module . '_' . $hook; if (function_exists($function)) { $result = call_user_func_array($function, $args); @@ -466,6 +369,41 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { } /** + * Provides information about modules' implementations of a hook. + * + * @param $hook + * The name of the hook (e.g. "help" or "menu"). + * + * @return + * An array whose keys are the names of the modules which are implementing + * this hook and whose values are either an array of information from + * hook_hook_info or FALSE if the implementation is in the module file. + * + */ + protected function getImplementationInfo($hook) { + $this->implementations = $this->implementations ?: array(); + if (isset($this->implementations[$hook])) { + return $this->implementations[$hook]; + } + $hook_info = $this->getHookInfo(); + $this->implementations[$hook] = array(); + foreach ($this->moduleList as $module => $filename) { + $include_file = isset($hook_info[$hook]['group']) && $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']); + // Since $this->hookImplements() may needlessly try to load the include + // file again, function_exists() is used directly here. + if (function_exists($module . '_' . $hook)) { + $this->implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; + } + } + // Allow modules to change the weight of specific implementations but avoid + // an infinite loop. + if ($hook != 'module_implements_alter') { + $this->alter('module_implements', $this->implementations[$hook], $hook); + } + return $this->implementations[$hook]; + } + + /** * Retrieves a list of hooks that are declared through hook_hook_info(). * * @return @@ -476,45 +414,28 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { * @see hook_hook_info() */ protected function getHookInfo() { - // When this function is indirectly invoked from bootstrap_invoke_all() prior - // to all modules being loaded, we do not want to cache an incomplete - // hook_hookInfo() result, so instead return an empty array. This requires - // bootstrap hook implementations to reside in the .module file, which is - // optimal for performance anyway. - if (!$this->loaded) { - return array(); + if ($this->hookInfo) { + return $this->hookInfo; } - if (!isset($this->hookInfo)) { - $this->hookInfo = array(); - $cache = $this->bootstrapCache->get('hook_info'); - if ($cache === FALSE) { - // Rebuild the cache and save it. - // We can't use $this->invokeAll() here or it would cause an infinite - // loop. - foreach ($this->moduleList as $module => $filename) { - $function = $module . '_hook_info'; - if (function_exists($function)) { - $result = $function(); - - if (isset($result) && is_array($result)) { - $this->hookInfo = NestedArray::mergeDeep($this->hookInfo, $result); - } - } - } - // We can't use $this->alter() for the same reason as above. - foreach ($this->moduleList as $module => $filename) { - $function = $module . '_hook_info_alter'; - if (function_exists($function)) { - $function($this->hookInfo); - } + $this->hookInfo = array(); + // We can't use $this->invokeAll() here or it would cause an infinite + // loop. + foreach ($this->moduleList as $module => $filename) { + $function = $module . '_hook_info'; + if (function_exists($function)) { + $result = $function(); + if (isset($result) && is_array($result)) { + $this->hookInfo = NestedArray::mergeDeep($this->hookInfo, $result); } - $this->bootstrapCache->set('hook_info', $this->hookInfo); } - else { - $this->hookInfo = $cache->data; + } + // We can't use $this->alter() for the same reason as above. + foreach ($this->moduleList as $module => $filename) { + $function = $module . '_hook_info_alter'; + if (function_exists($function)) { + $function($this->hookInfo); } } - return $this->hookInfo; } diff --git a/core/lib/Drupal/Core/ModuleHandlerInterface.php b/core/lib/Drupal/Core/ModuleHandlerInterface.php index 6d575a0..ed8daff 100644 --- a/core/lib/Drupal/Core/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/ModuleHandlerInterface.php @@ -68,6 +68,11 @@ public function getModuleList(); public function setModuleList(array $module_list = array()); /** + * Retrieves the list of bootstrap modules. + */ + public function getBootstrapModules(); + + /** * Determines which modules require and are required by each module. * * @param $modules @@ -157,13 +162,6 @@ public function resetImplementations(); public function implementsHook($module, $hook); /** - * Writes the hook implementation cache. - * - * @see $this->getImplementations() - */ - public function writeImplementationsCache(); - - /** * Invokes a hook in all enabled modules that implement it. * * @param $hook @@ -175,7 +173,7 @@ public function writeImplementationsCache(); * An array of return values of the hook implementations. If modules return * arrays from their implementations, those are merged into one array. */ - public function invokeAll($hook, $args); + public function invokeAll($hook, $args = array()); /** * Passes alterable variables to specific hook_TYPE_alter() implementations.