summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcatch2012-10-22 09:14:49 (GMT)
committercatch2012-10-22 09:14:49 (GMT)
commitafc4b899455ce0683591de7bd58581e9df5c2e41 (patch)
tree3055818570f16e24bdf7eaae88c75069f8367157
parent408b1cf05ffad1745808b31a9c138fe4efa4eb70 (diff)
Issue #1706064 by chx, katbailey, effulgentsia, podarok et al: Compile the Injection Container to disk.
-rw-r--r--core/includes/bootstrap.inc4
-rw-r--r--core/includes/common.inc5
-rw-r--r--core/includes/file.inc52
-rw-r--r--core/includes/install.core.inc8
-rw-r--r--core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php14
-rw-r--r--core/lib/Drupal/Component/PhpStorage/FileStorage.php23
-rw-r--r--core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php12
-rw-r--r--core/lib/Drupal/Core/CoreBundle.php61
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterMatchersPass.php35
-rw-r--r--core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterNestedMatchersPass.php35
-rw-r--r--core/lib/Drupal/Core/DrupalKernel.php134
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php23
-rw-r--r--core/modules/simpletest/lib/Drupal/simpletest/TestBase.php2
-rw-r--r--core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php111
-rw-r--r--core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/BundleTestBundle.php4
-rwxr-xr-xcore/scripts/run-tests.sh2
-rw-r--r--index.php2
17 files changed, 443 insertions, 84 deletions
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index fdcb01b..dfb61e1 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -894,7 +894,7 @@ function drupal_get_filename($type, $name, $filename = NULL) {
// Verify that we have an keyvalue service before using it. This is required
// because this function is called during installation.
// @todo Inject database connection into KeyValueStore\DatabaseStorage.
- if (drupal_container()->hasDefinition('keyvalue') && function_exists('db_query')) {
+ if (drupal_container()->has('keyvalue') && function_exists('db_query')) {
try {
$file_list = state()->get('system.' . $type . '.files');
if ($file_list && isset($file_list[$name]) && file_exists(DRUPAL_ROOT . '/' . $file_list[$name])) {
@@ -2442,7 +2442,7 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
// @todo Replace this with a cache.factory service plus 'config' argument.
$container
- ->register('cache.config')
+ ->register('cache.config', 'Drupal\Core\Cache\CacheBackendInterface')
->setFactoryClass('Drupal\Core\Cache\CacheFactory')
->setFactoryMethod('get')
->addArgument('config');
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 0f252ca..6800fa5 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3901,7 +3901,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
// this is an AJAX request.
// @todo Clean up container call.
$container = drupal_container();
- if ($container->has('request') && $container->has('content_negotiation')) {
+ if ($container->has('content_negotiation') && $container->isScopeActive('request')) {
$type = $container->get('content_negotiation')->getContentType($container->get('request'));
}
if (!empty($items['settings']) || (!empty($type) && $type == 'ajax')) {
@@ -6853,6 +6853,9 @@ function drupal_flush_all_caches() {
drupal_container()->get('router.builder')->rebuild();
menu_router_rebuild();
+ // Wipe the DIC cache.
+ drupal_php_storage('service_container')->deleteAll();
+
// Re-initialize the maintenance theme, if the current request attempted to
// use it. Unlike regular usages of this function, the installer and update
// scripts need to flush all caches during GET requests/page building.
diff --git a/core/includes/file.inc b/core/includes/file.inc
index 977d186..16ed619 100644
--- a/core/includes/file.inc
+++ b/core/includes/file.inc
@@ -1441,32 +1441,40 @@ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
$options['key'] = in_array($options['key'], array('uri', 'filename', 'name')) ? $options['key'] : 'uri';
$files = array();
- if (is_dir($dir) && $handle = opendir($dir)) {
- while (FALSE !== ($filename = readdir($handle))) {
- if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') {
- $uri = "$dir/$filename";
- $uri = file_stream_wrapper_uri_normalize($uri);
- if (is_dir($uri) && $options['recurse']) {
- // Give priority to files in this folder by merging them in after any subdirectory files.
- $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
- }
- elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
- // Always use this match over anything already set in $files with the
- // same $$options['key'].
- $file = new stdClass();
- $file->uri = $uri;
- $file->filename = $filename;
- $file->name = pathinfo($filename, PATHINFO_FILENAME);
- $key = $options['key'];
- $files[$file->$key] = $file;
- if ($options['callback']) {
- $options['callback']($uri);
+ // Avoid warnings when opendir does not have the permissions to open a
+ // directory.
+ if (is_dir($dir)) {
+ if($handle = @opendir($dir)) {
+ while (FALSE !== ($filename = readdir($handle))) {
+ if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') {
+ $uri = "$dir/$filename";
+ $uri = file_stream_wrapper_uri_normalize($uri);
+ if (is_dir($uri) && $options['recurse']) {
+ // Give priority to files in this folder by merging them in after
+ // any subdirectory files.
+ $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);
+ }
+ elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) {
+ // Always use this match over anything already set in $files with
+ // the same $options['key'].
+ $file = new stdClass();
+ $file->uri = $uri;
+ $file->filename = $filename;
+ $file->name = pathinfo($filename, PATHINFO_FILENAME);
+ $key = $options['key'];
+ $files[$file->$key] = $file;
+ if ($options['callback']) {
+ $options['callback']($uri);
+ }
}
}
}
- }
- closedir($handle);
+ closedir($handle);
+ }
+ else {
+ watchdog('file', '@dir can not be opened', array('@dir' => $dir), WATCHDOG_ERROR);
+ }
}
return $files;
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 0fcdd94..3968682 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -1495,11 +1495,9 @@ function install_bootstrap_full(&$install_state) {
// Clear the module list that was overriden earlier in the process.
// This will allow all freshly installed modules to be loaded.
module_list_reset();
- // @todo The constructor parameters for the Kernel class are for environment,
- // e.g. 'prod', 'dev', and a boolean indicating whether it is in debug mode.
- // Drupal does not currently make use of either of these, though that may
- // change with http://drupal.org/node/1537198.
- $kernel = new DrupalKernel('prod', FALSE);
+
+ // Instantiate the kernel.
+ $kernel = new DrupalKernel('prod', FALSE, NULL);
$kernel->boot();
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
}
diff --git a/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php b/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php
index 3d1bd95..bfb7a2a 100644
--- a/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php
+++ b/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php
@@ -69,4 +69,18 @@ class FileReadOnlyStorage implements PhpStorageInterface {
protected function getFullPath($name) {
return $this->directory . '/' . $name;
}
+
+ /**
+ * Implements Drupal\Component\PhpStorage\PhpStorageInterface::writeable().
+ */
+ function writeable() {
+ return FALSE;
+ }
+
+ /**
+ * Implements Drupal\Component\PhpStorage\PhpStorageInterface::deleteAll().
+ */
+ public function deleteAll() {
+ return FALSE;
+ }
}
diff --git a/core/lib/Drupal/Component/PhpStorage/FileStorage.php b/core/lib/Drupal/Component/PhpStorage/FileStorage.php
index 80f3ec4..a07c27e 100644
--- a/core/lib/Drupal/Component/PhpStorage/FileStorage.php
+++ b/core/lib/Drupal/Component/PhpStorage/FileStorage.php
@@ -71,4 +71,27 @@ class FileStorage implements PhpStorageInterface {
protected function getFullPath($name) {
return $this->directory . '/' . $name;
}
+
+ /**
+ * Implements Drupal\Component\PhpStorage\PhpStorageInterface::writeable().
+ */
+ function writeable() {
+ return TRUE;
+ }
+
+ /**
+ * Implements Drupal\Component\PhpStorage\PhpStorageInterface::deleteAll().
+ */
+ function deleteAll() {
+ return file_unmanaged_delete_recursive($this->directory, array(__CLASS__, 'filePreDeleteCallback'));
+ }
+
+ /**
+ * Ensures files and directories are deletable.
+ */
+ public static function filePreDeleteCallback($path) {
+ if (file_exists($path)) {
+ chmod($path, 0700);
+ }
+ }
}
diff --git a/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php b/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php
index 1eaece3..03ca812 100644
--- a/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php
+++ b/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php
@@ -57,6 +57,13 @@ interface PhpStorageInterface {
public function save($name, $code);
/**
+ * Whether this is a writeable storage.
+ *
+ * @return bool
+ */
+ public function writeable();
+
+ /**
* Deletes PHP code from storage.
*
* @param string $name
@@ -66,4 +73,9 @@ interface PhpStorageInterface {
* TRUE if the delete succeeded, FALSE if it failed.
*/
public function delete($name);
+
+ /**
+ * Removes all files in this bin.
+ */
+ public function deleteAll();
}
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index f1e43d6..aeb270c 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -8,6 +8,8 @@
namespace Drupal\Core;
use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
@@ -26,6 +28,7 @@ use Drupal\Core\Database\Database;
*/
class CoreBundle extends Bundle
{
+
public function build(ContainerBuilder $container) {
// The 'request' scope and service enable services to depend on the Request
@@ -61,41 +64,34 @@ class CoreBundle extends Bundle
->addArgument(new Reference('database'))
->addArgument(new Reference('lock'));
- $container->register('router.dumper', '\Drupal\Core\Routing\MatcherDumper')
+ $container->register('router.dumper', 'Drupal\Core\Routing\MatcherDumper')
->addArgument(new Reference('database'));
$container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
->addArgument(new Reference('router.dumper'));
- // @todo Replace below lines with the commented out block below it when it's
- // performant to do so: http://drupal.org/node/1706064.
- $dispatcher = $container->get('dispatcher');
- $matcher = new \Drupal\Core\Routing\ChainMatcher();
- $matcher->add(new \Drupal\Core\LegacyUrlMatcher());
-
- $nested = new \Drupal\Core\Routing\NestedMatcher();
- $nested->setInitialMatcher(new \Drupal\Core\Routing\PathMatcher(Database::getConnection()));
- $nested->addPartialMatcher(new \Drupal\Core\Routing\HttpMethodMatcher());
- $nested->setFinalMatcher(new \Drupal\Core\Routing\FirstEntryFinalMatcher());
- $matcher->add($nested, 5);
+ $container->register('matcher', 'Drupal\Core\Routing\ChainMatcher');
+ $container->register('legacy_url_matcher', 'Drupal\Core\LegacyUrlMatcher')
+ ->addTag('chained_matcher');
+ $container->register('nested_matcher', 'Drupal\Core\Routing\NestedMatcher')
+ ->addTag('chained_matcher', array('priority' => 5));
- $content_negotation = new \Drupal\Core\ContentNegotiation();
- $dispatcher->addSubscriber(new \Symfony\Component\HttpKernel\EventListener\RouterListener($matcher));
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\ViewSubscriber($content_negotation));
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\AccessSubscriber());
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\MaintenanceModeSubscriber());
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\PathSubscriber());
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\LegacyRequestSubscriber());
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\LegacyControllerSubscriber());
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\FinishResponseSubscriber());
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\RequestCloseSubscriber());
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber());
- $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\RouteProcessorSubscriber());
- $container->set('content_negotiation', $content_negotation);
- $dispatcher->addSubscriber(\Drupal\Core\ExceptionController::getExceptionListener($container));
+ // The following services are tagged as 'nested_matcher' services and are
+ // processed in the RegisterNestedMatchersPass compiler pass. Each one
+ // needs to be set on the matcher using a different method, so we use a
+ // tag attribute, 'method', which can be retrieved and passed to the
+ // addMethodCall() method that gets called on the matcher service in the
+ // compiler pass.
+ $container->register('path_matcher', 'Drupal\Core\Routing\PathMatcher')
+ ->addArgument(new Reference('database'))
+ ->addTag('nested_matcher', array('method' => 'setInitialMatcher'));
+ $container->register('http_method_matcher', 'Drupal\Core\Routing\HttpMethodMatcher')
+ ->addTag('nested_matcher', array('method' => 'addPartialMatcher'));
+ $container->register('first_entry_final_matcher', 'Drupal\Core\Routing\FirstEntryFinalMatcher')
+ ->addTag('nested_matcher', array('method' => 'setFinalMatcher'));
- /*
- $container->register('matcher', 'Drupal\Core\LegacyUrlMatcher');
- $container->register('router_listener', 'Drupal\Core\EventSubscriber\RouterListener')
+ $container->register('router_processor_subscriber', 'Drupal\Core\EventSubscriber\RouteProcessorSubscriber')
+ ->addTag('kernel.event_subscriber');
+ $container->register('router_listener', 'Symfony\Component\HttpKernel\EventListener\RouterListener')
->addArgument(new Reference('matcher'))
->addTag('kernel.event_subscriber');
$container->register('content_negotiation', 'Drupal\Core\ContentNegotiation');
@@ -118,15 +114,18 @@ class CoreBundle extends Bundle
->addTag('kernel.event_subscriber');
$container->register('request_close_subscriber', 'Drupal\Core\EventSubscriber\RequestCloseSubscriber')
->addTag('kernel.event_subscriber');
- $container->register('config_global_override_subscriber', '\Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber');
+ $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber')
+ ->addTag('kernel.event_subscriber');
$container->register('exception_listener', 'Drupal\Core\EventSubscriber\ExceptionListener')
->addTag('kernel.event_subscriber')
->addArgument(new Reference('service_container'))
->setFactoryClass('Drupal\Core\ExceptionController')
->setFactoryMethod('getExceptionListener');
+ $container->addCompilerPass(new RegisterMatchersPass());
+ $container->addCompilerPass(new RegisterNestedMatchersPass());
// Add a compiler pass for registering event subscribers.
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
- */
}
+
}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterMatchersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterMatchersPass.php
new file mode 100644
index 0000000..793394b
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterMatchersPass.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Adds services tagged 'chained_matcher' to the 'matcher' service.
+ */
+class RegisterMatchersPass implements CompilerPassInterface {
+
+ /**
+ * Adds services tagged 'chained_matcher' to the 'matcher' service.
+ *
+ * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+ * The container to process.
+ */
+ public function process(ContainerBuilder $container) {
+ if (!$container->hasDefinition('matcher')) {
+ return;
+ }
+ $matcher = $container->getDefinition('matcher');
+ foreach ($container->findTaggedServiceIds('chained_matcher') as $id => $attributes) {
+ $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
+ $matcher->addMethodCall('add', array(new Reference($id), $priority));
+ }
+ }
+}
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterNestedMatchersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterNestedMatchersPass.php
new file mode 100644
index 0000000..b245952
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterNestedMatchersPass.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Adds services tagged 'nested_matcher' to the tagged_matcher service.
+ */
+class RegisterNestedMatchersPass implements CompilerPassInterface {
+
+ /**
+ * Adds services tagged 'nested_matcher' to the tagged_matcher service.
+ *
+ * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+ * The container to process.
+ */
+ public function process(ContainerBuilder $container) {
+ if (!$container->hasDefinition('nested_matcher')) {
+ return;
+ }
+ $nested = $container->getDefinition('nested_matcher');
+ foreach ($container->findTaggedServiceIds('nested_matcher') as $id => $attributes) {
+ $method = $attributes[0]['method'];
+ $nested->addMethodCall($method, array(new Reference($id)));
+ }
+ }
+}
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 869d4ee..e2991e1 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -7,11 +7,14 @@
namespace Drupal\Core;
+use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\CoreBundle;
+use Drupal\Component\PhpStorage\PhpStorageInterface;
use Symfony\Component\HttpKernel\Kernel;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
+use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
/**
* The DrupalKernel class is the core of Drupal itself.
@@ -26,6 +29,60 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
class DrupalKernel extends Kernel {
/**
+ * Holds the list of enabled modules.
+ *
+ * @var array
+ */
+ protected $moduleList;
+
+ /**
+ * Cache object for getting or setting the compiled container's class name.
+ *
+ * @var \Drupal\Core\Cache\CacheBackendInterface
+ */
+ protected $compilationIndexCache;
+
+ /**
+ * PHP code storage object to use for the compiled container.
+ *
+ * @var \Drupal\Component\PhpStorage\PhpStorageInterface
+ */
+ protected $storage;
+
+ /**
+ * Constructs a DrupalKernel object.
+ *
+ * @param string $environment
+ * String indicating the environment, e.g. 'prod' or 'dev'. Used by
+ * Symfony\Component\HttpKernel\Kernel::__construct(). Drupal does not use
+ * this value currently. Pass 'prod'.
+ * @param bool $debug
+ * 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 $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 $module_list = NULL, CacheBackendInterface $compilation_index_cache = NULL) {
+ parent::__construct($environment, $debug);
+ $this->compilationIndexCache = $compilation_index_cache;
+ $this->storage = drupal_php_storage('service_container');
+ 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->moduleList = module_list();
+ }
+ }
+
+ /**
* Overrides Kernel::init().
*/
public function init() {
@@ -43,10 +100,7 @@ class DrupalKernel extends Kernel {
new CoreBundle(),
);
- // @todo Remove the necessity of calling system_list() to find out which
- // bundles exist. See http://drupal.org/node/1331486
- $modules = array_keys(system_list('module_enabled'));
- foreach ($modules as $module) {
+ foreach ($this->moduleList as $module) {
$camelized = ContainerBuilder::camelize($module);
$class = "Drupal\\{$module}\\{$camelized}Bundle";
if (class_exists($class)) {
@@ -61,12 +115,43 @@ class DrupalKernel extends Kernel {
* Initializes the service container.
*/
protected function initializeContainer() {
- // @todo We should be compiling the container and dumping to php so we don't
- // have to recompile every time. There is a separate issue for this, see
- // http://drupal.org/node/1668892.
- $this->container = $this->buildContainer();
+ $this->container = NULL;
+ 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->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';
+ }
+ }
+
$this->container->set('kernel', $this);
+
drupal_container($this->container);
+
+ if (isset($error)) {
+ watchdog('DrupalKernel', $error);
+ }
}
/**
@@ -84,10 +169,7 @@ class DrupalKernel extends Kernel {
foreach ($this->bundles as $bundle) {
$bundle->build($container);
}
-
- // @todo Compile the container: http://drupal.org/node/1706064.
- //$container->compile();
-
+ $container->compile();
return $container;
}
@@ -101,6 +183,34 @@ class DrupalKernel extends Kernel {
}
/**
+ * Dumps the service container to PHP code in the config directory.
+ *
+ * This method is based on the dumpContainer method in the parent class, but
+ * that method is reliant on the Config component which we do not use here.
+ *
+ * @param ContainerBuilder $container
+ * The service container.
+ * @param string $baseClass
+ * The name of the container's base class
+ *
+ * @return bool
+ * TRUE if the container was successfully dumped to disk.
+ */
+ protected function dumpDrupalContainer(ContainerBuilder $container, $baseClass) {
+ if (!$this->storage->writeable()) {
+ return FALSE;
+ }
+ // Cache the container.
+ $dumper = new PhpDumper($container);
+ $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($class . '.php', $content);
+ }
+
+ /**
* Overrides and eliminates this method from the parent class. Do not use.
*
* This method is part of the KernelInterface interface, but takes an object
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index 8065349..a4ec691 100644
--- a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
@@ -7,6 +7,7 @@
namespace Drupal\Core\EventSubscriber;
+use Drupal\Core\Language\LanguageManager;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -17,6 +18,23 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class FinishResponseSubscriber implements EventSubscriberInterface {
/**
+ * The LanguageManager object for retrieving the correct language code.
+ *
+ * @var LanguageManager
+ */
+ protected $languageManager;
+
+ /**
+ * Constructs a FinishResponseSubscriber object.
+ *
+ * @param LanguageManager $language_manager
+ * The LanguageManager object for retrieving the correct language code.
+ */
+ public function __construct(LanguageManager $language_manager) {
+ $this->languageManager = $language_manager;
+ }
+
+ /**
* Sets extra headers on successful responses.
*
* @param Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
@@ -30,10 +48,7 @@ class FinishResponseSubscriber implements EventSubscriberInterface {
$response->headers->set('X-UA-Compatible', 'IE=edge,chrome=1', false);
// Set the Content-language header.
- // @todo Receive the LanguageManager object as a constructor argument when
- // the dependency injection container allows for it performantly:
- // http://drupal.org/node/1706064.
- $response->headers->set('Content-language', language(LANGUAGE_TYPE_INTERFACE)->langcode);
+ $response->headers->set('Content-language', $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE)->langcode);
// Because pages are highly dynamic, set the last-modified time to now
// since the page is in fact being regenerated right now.
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index dc6dae4..9d151e0 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -911,7 +911,7 @@ abstract class TestBase {
// container in drupal_container(). Drupal\simpletest\TestBase::tearDown()
// restores the original container.
// @see Drupal\Core\DrupalKernel::initializeContainer()
- $this->kernel = new DrupalKernel('testing', 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 --git a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php
new file mode 100644
index 0000000..41070c3
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\DrupalKernel\DrupalKernelTest.
+ */
+
+namespace Drupal\system\Tests\DrupalKernel;
+
+use Drupal\Core\Cache\MemoryBackend;
+use Drupal\Core\DrupalKernel;
+use Drupal\simpletest\UnitTestBase;
+use ReflectionClass;
+
+/**
+ * Tests compilation of the DIC.
+ */
+class DrupalKernelTest extends UnitTestBase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'DrupalKernel tests',
+ 'description' => 'Tests DIC compilation to disk.',
+ 'group' => 'DrupalKernel',
+ );
+ }
+
+ /**
+ * Tests DIC compilation.
+ */
+ function testCompileDIC() {
+ // Because we'll be instantiating a new kernel during this test, the
+ // container stored in drupal_container() will be updated as a side effect.
+ // We need to be able to restore it to the correct one at the end of this
+ // test.
+ $original_container = drupal_container();
+ global $conf;
+ $conf['php_storage']['service_container'] = array(
+ 'class' => 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage',
+ 'secret' => $GLOBALS['drupal_hash_salt'],
+ );
+ $cache = new MemoryBackend('test');
+ $module_enabled = array(
+ 'system' => 'system',
+ 'user' => 'user',
+ );
+ $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, $module_enabled, $cache);
+ $kernel->boot();
+ $container = $kernel->getContainer();
+ $refClass = new ReflectionClass($container);
+ $is_compiled_container =
+ $refClass->getParentClass()->getName() == 'Symfony\Component\DependencyInjection\Container' &&
+ !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
+ $this->assertTrue($is_compiled_container);
+
+ // Reset the container.
+ drupal_container(NULL, TRUE);
+
+ // Now use the read-only storage implementation, simulating a "production"
+ // environment.
+ drupal_static_reset('drupal_php_storage');
+ $conf['php_storage']['service_container'] = array(
+ 'class' => 'Drupal\Component\PhpStorage\FileReadOnlyStorage',
+ );
+ $kernel = new DrupalKernel('testing', FALSE, $module_enabled, $cache);
+ $kernel->boot();
+ $container = $kernel->getContainer();
+ $refClass = new ReflectionClass($container);
+ $is_compiled_container =
+ $refClass->getParentClass()->getName() == 'Symfony\Component\DependencyInjection\Container' &&
+ !$refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
+ $this->assertTrue($is_compiled_container);
+
+ // We make this assertion here purely to show that the new container below
+ // is functioning correctly, i.e. we get a brand new ContainerBuilder
+ // which has the required new services, after changing the list of enabled
+ // modules.
+ $this->assertFalse($container->has('bundle_test_class'));
+
+ // Reset the container.
+ drupal_container(NULL, TRUE);
+
+ // 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',
+ );
+ $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, $module_enabled, $cache);
+ $kernel->boot();
+ $container = $kernel->getContainer();
+ $refClass = new ReflectionClass($container);
+ $is_container_builder = $refClass->isSubclassOf('Symfony\Component\DependencyInjection\ContainerBuilder');
+ $this->assertTrue($is_container_builder);
+ // Assert that the new module's bundle was registered to the new container.
+ $this->assertTrue($container->has('bundle_test_class'));
+
+ // Restore the original container.
+ drupal_container($original_container);
+ }
+}
diff --git a/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/BundleTestBundle.php b/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/BundleTestBundle.php
index 97b79c1..c2db16a 100644
--- a/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/BundleTestBundle.php
+++ b/core/modules/system/tests/modules/bundle_test/lib/Drupal/bundle_test/BundleTestBundle.php
@@ -23,9 +23,5 @@ class BundleTestBundle extends Bundle
// Override a default bundle used by core to a dummy class.
$container->register('file.usage', 'Drupal\bundle_test\TestFileUsage');
-
- // @todo Remove when the 'kernel.event_subscriber' tag above is made to
- // work: http://drupal.org/node/1706064.
- $container->get('dispatcher')->addSubscriber($container->get('bundle_test_class'));
}
}
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index 2395a84..5aa8b22 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -479,7 +479,7 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
// simpletest_clean_temporary_directories() cannot be used here, since it
// would also delete file directories of other tests that are potentially
// running concurrently.
- file_unmanaged_delete_recursive($test_directory);
+ file_unmanaged_delete_recursive($test_directory, array('Drupal\simpletest\TestBase', 'filePreDeleteCallback'));
$messages[] = "- Removed test files directory.";
}
diff --git a/index.php b/index.php
index 38177ab..a6eb61e 100644
--- a/index.php
+++ b/index.php
@@ -28,7 +28,7 @@ require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
// @todo Figure out how best to handle the Kernel constructor parameters.
-$kernel = new DrupalKernel('prod', FALSE);
+$kernel = new DrupalKernel('prod', FALSE, NULL, cache('bootstrap'));
// Create a request object from the HTTPFoundation.
$request = Request::createFromGlobals();