diff -u b/core/includes/install.core.inc b/core/includes/install.core.inc --- b/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1465,8 +1465,8 @@ // This will allow all freshly installed modules to be loaded. module_list_reset(); - // Instantiate a new kernel that will not compile the container. - $kernel = new DrupalKernel('prod', FALSE, NULL, FALSE); + // Instantiate the kernel. + $kernel = new DrupalKernel('prod', FALSE, NULL); $kernel->boot(); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); } diff -u b/core/includes/module.inc b/core/includes/module.inc --- b/core/includes/module.inc +++ b/core/includes/module.inc @@ -119,8 +119,7 @@ * Builds a list of bootstrap modules and enabled modules and themes. * * @param $type - * (optional) The type of list to return. If NULL, an associative array of - * all lists will be returned, keyed by the type (see below). + * The type of list to return: * - module_enabled: All enabled modules. * - bootstrap: All enabled modules required for bootstrap. * - theme: All themes. @@ -139,7 +138,7 @@ * callers like system_list() to force-disable a possible configuration * storage controller cache or some other way to circumvent it/take it over. */ -function system_list($type = NULL) { +function system_list($type) { $lists = &drupal_static(__FUNCTION__); // For bootstrap modules, attempt to fetch the list from cache if possible. @@ -250,9 +249,6 @@ // Set the theme engine prefix. $lists['theme'][$key]->prefix = ($lists['theme'][$key]->info['engine'] == 'theme') ? $base_key : $lists['theme'][$key]->info['engine']; } - // Store a hash of the enabled modules and themes for use when compiling - // the DIC. - $lists['system_list_hash'] = hash('sha256', implode(',', array_keys($lists['filepaths']))); // Save a little space by stripping out the keys from the file paths. $lists['filepaths'] = array_values($lists['filepaths']); cache('bootstrap')->set('system_list', $lists); @@ -265,7 +261,7 @@ } } - return $type ? $lists[$type] : $lists; + return $lists[$type]; } /** diff -u b/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php --- b/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -7,6 +7,7 @@ namespace Drupal\Core; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\CoreBundle; use Drupal\Component\PhpStorage\PhpStorageInterface; use Symfony\Component\HttpKernel\Kernel; @@ -32,14 +33,14 @@ * * @var array */ - protected $systemList; + protected $moduleList; /** - * Whether to use a compiled Container as opposed to a ContainerBuilder. + * Drupal cache object for getting or setting the class name of the compiled container. * - * @var boolean + * @var \Drupal\Core\Cache\CacheBackendInterface */ - protected $useCompiledContainer; + protected $compilationIndexCache; /** * PHP code storage object to use for the compiled container. @@ -59,27 +60,25 @@ * Boolean indicating whether we are in debug mode; Used by * Symfony\Component\HttpKernel\Kernel::__construct(). Drupal does not use * this value currently. Pass TRUE. - * @param array $system_list - * The same data structure as system_list(). - * @param bool $use_compiled_container - * Whether to compile the container to disk or not. - * - * @todo Drupal does not currently make use of either the $environment or - * $debug parameters that are used in the parent's constructor, though this - * may change with http://drupal.org/node/1537198. + * @param array $module_list + * (optional) The array of enabled modules as returned by module_list(). + * @param Drupal\Core\Cache\CacheBackendInterface $compilation_index_cache + * (optional) If wanting to dump a compiled container to disk or use a + * previously compiled container, the cache object for the bin that stores + * the class name of the compiled container. */ - public function __construct($environment, $debug, array $system_list = NULL, $use_compiled_container = TRUE) { + public function __construct($environment, $debug, array $module_list = NULL, CacheBackendInterface $compilation_index_cache = NULL) { parent::__construct($environment, $debug); - $this->useCompiledContainer = $use_compiled_container; + $this->compilationIndexCache = $compilation_index_cache; $this->storage = drupal_php_storage('service_container'); - if (isset($system_list)) { - $this->systemList = $system_list; + if (isset($module_list)) { + $this->moduleList = $module_list; } else { // @todo This is a temporary measure which will no longer be necessary // once we have an ExtensionHandler for managing this list. See // http://drupal.org/node/1331486. - $this->systemList = system_list(); + $this->moduleList = module_list(); } } @@ -101,8 +100,7 @@ new CoreBundle(), ); - $modules = $this->systemList['module_enabled']; - foreach ($modules as $module) { + foreach ($this->moduleList as $module) { $camelized = ContainerBuilder::camelize($module); $class = "Drupal\\{$module}\\{$camelized}Bundle"; if (class_exists($class)) { @@ -115,42 +113,32 @@ /** * Initializes the service container. - * - * @todo We are compiling the container and dumping to a PHP file whose name - * is based on the list of enabled modules. A new file is created when this - * list changes but there is currently no garbage collection in place for - * the old files. See http://drupal.org/node/1759582. */ protected function initializeContainer() { $this->container = NULL; - if ($this->useCompiledContainer) { - // While the default Symfony class name only depends on the environment, for - // testing purposes we can't use that because there would be a collision as - // each test method creates a new kernel. On the other hand, the container - // only depends on the enabled modules (and only on those that provide - // bundles) so we base the name of the container class on the hash of the - // enabled modules. We can't directly use the hash though because PHP - // identifiers always start with a letter and hashes don't so we add a - // character to the beginning. This mechanism also avoids the problem of - // needing to rebuild the DIC at the right time on module enable: simply on - // the next request the hash will change and so the container will be - // rebuilt. - $class = 'c' . $this->systemList['system_list_hash'] . ucfirst($this->environment) . ($this->debug ? 'Debug' : ''); - $cache_file = $class . '.php'; - - // First, try to load. - if (!class_exists($class, FALSE)) { - $this->storage->load($cache_file); - } - // If the load succeeded or the class already existed, use it. - if (class_exists($class, FALSE)) { - $fully_qualified_class_name = '\\' . $class; - $this->container = new $fully_qualified_class_name; + if ($this->compilationIndexCache) { + // The name of the compiled container class is generated from the hash of + // its contents and cached. This enables multiple compiled containers + // (for example, for different states of which modules are enabled) to + // exist simultaneously on disk and in memory. + if ($cache = $this->compilationIndexCache->get(implode(':', array('service_container', $this->environment, $this->debug)))) { + $class = $cache->data; + $cache_file = $class . '.php'; + + // First, try to load. + if (!class_exists($class, FALSE)) { + $this->storage->load($cache_file); + } + // If the load succeeded or the class already existed, use it. + if (class_exists($class, FALSE)) { + $fully_qualified_class_name = '\\' . $class; + $this->container = new $fully_qualified_class_name; + } } } if (!isset($this->container)) { $this->container = $this->buildContainer(); - if ($this->useCompiledContainer && !$this->dumpDrupalContainer($cache_file, $this->container, $class, $this->getContainerBaseClass())) { + if ($this->compilationIndexCache && !$this->dumpDrupalContainer($this->container, $this->getContainerBaseClass())) { // We want to log this as an error but we cannot call watchdog() until // the container has been fully built and set in drupal_container(). $error = 'Container cannot be written to disk'; @@ -212,15 +200,18 @@ * @return bool * TRUE if the container was successfully dumped to disk. */ - protected function dumpDrupalContainer($cache_file, ContainerBuilder $container, $class, $baseClass) { + protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass) { if (!$this->storage->writeable()) { return FALSE; } // Cache the container. $dumper = new PhpDumper($container); - $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass)); + $content = $dumper->dump(array('class' => 'DrupalServiceContainerStub', 'base_class' => $baseClass)); + $class = 'c' . hash('sha256', $content); + $content = str_replace('DrupalServiceContainerStub', $class, $content); + $this->compilationIndexCache->set(implode(':', array('service_container', $this->environment, $this->debug)), $class); - return $this->storage->save($cache_file, $content); + return $this->storage->save($class . '.php', $content); } /** diff -u b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php --- b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php @@ -846,7 +846,7 @@ // container in drupal_container(). Drupal\simpletest\TestBase::tearDown() // restores the original container. // @see Drupal\Core\DrupalKernel::initializeContainer() - $this->kernel = new DrupalKernel('testing', FALSE, NULL, FALSE); + $this->kernel = new DrupalKernel('testing', FALSE, NULL); // Booting the kernel is necessary to initialize the new DIC. While // normally the kernel gets booted on demand in // Symfony\Component\HttpKernel\handle(), this kernel needs manual booting diff -u b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php --- b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\DrupalKernel; +use Drupal\Core\Cache\MemoryBackend; use Drupal\Core\DrupalKernel; use Drupal\simpletest\UnitTestBase; use ReflectionClass; @@ -38,20 +39,16 @@ 'class' => 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage', 'secret' => $GLOBALS['drupal_hash_salt'], ); + $cache = new MemoryBackend('test'); $module_enabled = array( 'system' => 'system', 'user' => 'user', ); - $module_enabled_hash = hash('sha256', implode(',', $module_enabled)); - $system_list = array( - 'module_enabled' => $module_enabled, - 'system_list_hash' => $module_enabled_hash, - ); - $kernel = new DrupalKernel('testing', FALSE, $system_list); + $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache); $kernel->boot(); // Instantiate it a second time and we should get the compiled Container // class. - $kernel = new DrupalKernel('testing', FALSE, $system_list); + $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new ReflectionClass($container); @@ -69,7 +66,7 @@ $conf['php_storage']['service_container'] = array( 'class' => 'Drupal\Component\PhpStorage\FileReadOnlyStorage', ); - $kernel = new DrupalKernel('testing', FALSE, $system_list); + $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new ReflectionClass($container); @@ -87,24 +84,19 @@ // Reset the container. drupal_container(NULL, TRUE); - // Add another module so that a different hash is used for the class name - // and we can test that the new module's bundle is registered to the new - // container. + // Add another module so that we can test that the new module's bundle is + // registered to the new container. $module_enabled = array( 'system' => 'system', 'user' => 'user', 'bundle_test' => 'bundle_test', ); - $module_enabled_hash = hash('sha256', implode(',', array_keys($module_enabled))); - $system_list = array( - 'module_enabled' => $module_enabled, - 'system_list_hash' => $module_enabled_hash, - ); - $kernel = new DrupalKernel('testing', FALSE, $system_list); + $cache->flush(); + $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache); $kernel->boot(); // Instantiate it a second time and we should still get a ContainerBuilder // class because we are using the read-only PHP storage. - $kernel = new DrupalKernel('testing', FALSE, $system_list); + $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache); $kernel->boot(); $container = $kernel->getContainer(); $refClass = new ReflectionClass($container);