From afc4b899455ce0683591de7bd58581e9df5c2e41 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Mon, 22 Oct 2012 10:14:49 +0100
Subject: [PATCH] Issue #1706064 by chx, katbailey, effulgentsia, podarok et
 al: Compile the Injection Container to disk.

---
 core/includes/bootstrap.inc                   |   4 +-
 core/includes/common.inc                      |   5 +-
 core/includes/file.inc                        |  52 ++++---
 core/includes/install.core.inc                |   8 +-
 .../PhpStorage/FileReadOnlyStorage.php        |  14 ++
 .../Component/PhpStorage/FileStorage.php      |  23 +++
 .../PhpStorage/PhpStorageInterface.php        |  12 ++
 core/lib/Drupal/Core/CoreBundle.php           |  61 ++++----
 .../Compiler/RegisterMatchersPass.php         |  35 +++++
 .../Compiler/RegisterNestedMatchersPass.php   |  35 +++++
 core/lib/Drupal/Core/DrupalKernel.php         | 134 ++++++++++++++++--
 .../FinishResponseSubscriber.php              |  23 ++-
 .../lib/Drupal/simpletest/TestBase.php        |   2 +-
 .../Tests/DrupalKernel/DrupalKernelTest.php   | 111 +++++++++++++++
 .../Drupal/bundle_test/BundleTestBundle.php   |   4 -
 core/scripts/run-tests.sh                     |   2 +-
 index.php                                     |   2 +-
 17 files changed, 443 insertions(+), 84 deletions(-)
 create mode 100644 core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterMatchersPass.php
 create mode 100644 core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterNestedMatchersPass.php
 create mode 100644 core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php

diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index fdcb01bf4321..dfb61e1514fa 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 0f252cacfd62..6800fa5dda31 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 977d18636ae6..16ed6192f711 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 0fcdd946ba54..3968682c1320 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 3d1bd950862c..bfb7a2a07fc4 100644
--- a/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php
+++ b/core/lib/Drupal/Component/PhpStorage/FileReadOnlyStorage.php
@@ -69,4 +69,18 @@ public function delete($name) {
   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 80f3ec440311..a07c27e9a152 100644
--- a/core/lib/Drupal/Component/PhpStorage/FileStorage.php
+++ b/core/lib/Drupal/Component/PhpStorage/FileStorage.php
@@ -71,4 +71,27 @@ public function delete($name) {
   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 1eaece362038..03ca8124f3c6 100644
--- a/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php
+++ b/core/lib/Drupal/Component/PhpStorage/PhpStorageInterface.php
@@ -56,6 +56,13 @@ public function load($name);
    */
   public function save($name, $code);
 
+  /**
+   * Whether this is a writeable storage.
+   *
+   * @return bool
+   */
+  public function writeable();
+
   /**
    * Deletes PHP code from storage.
    *
@@ -66,4 +73,9 @@ public function save($name, $code);
    *   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 f1e43d64c31d..aeb270c75172 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 @@
  */
 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 @@ public function build(ContainerBuilder $container) {
       ->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 @@ public function build(ContainerBuilder $container) {
       ->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 000000000000..793394ba7f58
--- /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 000000000000..b245952c6330
--- /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 869d4ee36bdb..e2991e1c065e 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.
@@ -25,6 +28,60 @@
  */
 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().
    */
@@ -43,10 +100,7 @@ public function registerBundles() {
       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 @@ public function registerBundles() {
    * 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 @@ protected function buildContainer() {
     foreach ($this->bundles as $bundle) {
       $bundle->build($container);
     }
-
-    // @todo Compile the container: http://drupal.org/node/1706064.
-    //$container->compile();
-
+    $container->compile();
     return $container;
   }
 
@@ -100,6 +182,34 @@ protected function getContainerBuilder() {
     return new ContainerBuilder(new ParameterBag($this->getKernelParameters()));
   }
 
+  /**
+   * 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.
    *
diff --git a/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/FinishResponseSubscriber.php
index 8065349e204b..a4ec691a94b1 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;
@@ -16,6 +17,23 @@
  */
 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.
    *
@@ -30,10 +48,7 @@ public function onRespond(FilterResponseEvent $event) {
     $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 dc6dae427390..9d151e06e138 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -911,7 +911,7 @@ protected function rebuildContainer() {
     // 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 000000000000..41070c32bb59
--- /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 97b79c18afb6..c2db16a3fb41 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 @@ public function build(ContainerBuilder $container) {
 
     // 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 2395a846b18f..5aa8b22d0175 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 38177ab2b5ba..a6eb61e2a804 100644
--- a/index.php
+++ b/index.php
@@ -28,7 +28,7 @@
 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();
-- 
GitLab