From 10ab47224d5fd8466f2b9e215dfebafae56abb1b Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Mon, 21 Jan 2013 19:21:34 +0000
Subject: [PATCH] Issue #1331486 by katbailey, chx, sun, beejeebus, amateescu:
 Move module_invoke_*() and friends to an Extensions class.

---
 core/authorize.php                            |  10 +-
 core/includes/bootstrap.inc                   | 104 ++-
 core/includes/common.inc                      |  66 +-
 core/includes/install.core.inc                |  20 +-
 core/includes/install.inc                     |  13 +-
 core/includes/module.inc                      | 834 ++++--------------
 core/includes/schema.inc                      |  18 +-
 core/includes/theme.inc                       |  11 +-
 core/includes/theme.maintenance.inc           |   7 +-
 core/includes/update.inc                      |  39 +-
 core/lib/Drupal/Core/CoreBundle.php           |  43 +-
 .../Core/DependencyInjection/UpdateBundle.php |  37 +
 core/lib/Drupal/Core/DrupalKernel.php         |   3 +
 .../RequestCloseSubscriber.php                |  25 +-
 .../Core/Extension/CachedModuleHandler.php    | 167 ++++
 .../CachedModuleHandlerInterface.php          |  20 +
 .../Drupal/Core/Extension/ModuleHandler.php   | 522 +++++++++++
 .../Core/Extension/ModuleHandlerInterface.php | 238 +++++
 core/lib/Drupal/Core/Routing/RouteBuilder.php |  17 +-
 .../lib/Drupal/Core/Utility/ThemeRegistry.php |   6 +-
 core/modules/breakpoint/breakpoint.install    |   3 +-
 .../comment/Tests/CommentFieldsTest.php       |   4 +-
 .../Drupal/entity/Tests/EntityDisplayTest.php |   4 +-
 core/modules/field/field.module               |   2 +-
 core/modules/filter/filter.install            |   3 -
 .../lib/Drupal/forum/Tests/ForumTest.php      |   4 +-
 .../Tests/RdfSchemaSerializationTest.php      |   7 +-
 .../Tests/LanguageNegotiationInfoTest.php     |   2 +-
 .../layout/Plugin/Derivative/Layout.php       |   5 +-
 .../Drupal/locale/Tests/LocaleUpdateTest.php  |   2 +
 .../Drupal/simpletest/DrupalUnitTestBase.php  |  57 +-
 .../lib/Drupal/simpletest/TestBase.php        |  26 +-
 .../Tests/DrupalUnitTestBaseTest.php          |  90 +-
 .../lib/Drupal/simpletest/UnitTestBase.php    |   3 -
 .../Tests/Cache/DatabaseBackendUnitTest.php   |   5 +
 .../system/Tests/Module/EnableDisableTest.php |   2 +-
 .../system/Tests/Module/ModuleApiTest.php     |  33 +-
 .../system/Tests/Module/ModuleTestBase.php    |   5 +-
 .../Tests/System/MainContentFallbackTest.php  |   4 +-
 .../system/Tests/Theme/RegistryTest.php       |   2 +-
 .../Drupal/system/Tests/Theme/ThemeTest.php   |  10 +
 .../Upgrade/BareMinimalUpgradePathTest.php    |   2 +-
 .../ModulesDisabledUpgradePathTest.php        |   2 +-
 .../Tests/Upgrade/SystemUpgradePathTest.php   |   2 -
 .../Tests/Upgrade/UpgradePathTestBase.php     |   5 +-
 core/modules/system/system.admin.inc          |   4 +-
 core/modules/system/system.install            |   2 +-
 core/modules/system/system.module             |   4 +-
 core/scripts/dump-database-d6.sh              |   2 +-
 core/scripts/dump-database-d7.sh              |   2 +-
 core/update.php                               |  20 +-
 51 files changed, 1580 insertions(+), 938 deletions(-)
 create mode 100644 core/lib/Drupal/Core/DependencyInjection/UpdateBundle.php
 create mode 100644 core/lib/Drupal/Core/Extension/CachedModuleHandler.php
 create mode 100644 core/lib/Drupal/Core/Extension/CachedModuleHandlerInterface.php
 create mode 100644 core/lib/Drupal/Core/Extension/ModuleHandler.php
 create mode 100644 core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php

diff --git a/core/authorize.php b/core/authorize.php
index 46c6a29a3e2a..114dcd3ad1de 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -78,11 +78,11 @@ function authorize_access_allowed() {
 
 // We have to enable the user and system modules, even to check access and
 // display errors via the maintenance theme.
-$module_list['system']['filename'] = 'core/modules/system/system.module';
-$module_list['user']['filename'] = 'core/modules/user/user.module';
-module_list(NULL, $module_list);
-drupal_load('module', 'system');
-drupal_load('module', 'user');
+$module_list['system'] = 'core/modules/system/system.module';
+$module_list['user'] = 'core/modules/user/user.module';
+drupal_container()->get('module_handler')->setModuleList($module_list);
+drupal_container()->get('module_handler')->load('system');
+drupal_container()->get('module_handler')->load('user');
 
 // Initialize the language system.
 drupal_language_initialize();
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 577d857b9b41..676a94858745 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -888,6 +888,14 @@ function drupal_get_filename($type, $name, $filename = NULL) {
     // nothing
   }
   else {
+    if ($type == 'module') {
+      if (empty($files[$type])) {
+        $files[$type] = drupal_container()->get('module_handler')->getModuleList();
+      }
+      if (isset($files[$type][$name])) {
+        return $files[$type][$name];
+      }
+    }
     // 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.
@@ -1133,8 +1141,9 @@ function drupal_page_is_cacheable($allow_caching = NULL) {
  * @see bootstrap_hooks()
  */
 function bootstrap_invoke_all($hook) {
-  foreach (module_list('bootstrap') as $module) {
-    drupal_load('module', $module);
+  $module_handler = drupal_container()->get('module_handler');
+  foreach ($module_handler->getBootstrapModules() as $module) {
+    $module_handler->load($module);
     module_invoke($module, $hook);
   }
 }
@@ -1153,6 +1162,10 @@ function bootstrap_invoke_all($hook) {
  *   TRUE if the item is loaded or has already been loaded.
  */
 function drupal_load($type, $name) {
+  if ($type == 'module' && drupal_container()->get('module_handler')->moduleExists($name)) {
+    return drupal_container()->get('module_handler')->load($name);
+  }
+
   // Once a file is included this can't be reversed during a request so do not
   // use drupal_static() here.
   static $files = array();
@@ -2276,10 +2289,6 @@ function _drupal_exception_handler($exception) {
  * Sets up the script environment and loads settings.php.
  */
 function _drupal_bootstrap_configuration() {
-  // Set the Drupal custom error handler.
-  set_error_handler('_drupal_error_handler');
-  set_exception_handler('_drupal_exception_handler');
-
   drupal_environment_initialize();
   // Start a page timer:
   timer_start('page');
@@ -2294,6 +2303,11 @@ function _drupal_bootstrap_configuration() {
 
   // Load the procedural configuration system helper functions.
   require_once DRUPAL_ROOT . '/core/includes/config.inc';
+
+  // Set the Drupal custom error handler. (requires config())
+  set_error_handler('_drupal_error_handler');
+  set_exception_handler('_drupal_exception_handler');
+
   // Redirect the user to the installation script if Drupal has not been
   // installed yet (i.e., if no $databases array has been defined in the
   // settings.php file) and we are not already installing.
@@ -2423,7 +2437,7 @@ function _drupal_bootstrap_variables() {
   $conf = variable_initialize(isset($conf) ? $conf : array());
   // Load bootstrap modules.
   require_once DRUPAL_ROOT . '/core/includes/module.inc';
-  module_load_all(TRUE);
+  drupal_container()->get('module_handler')->loadBootstrapModules();
 }
 
 /**
@@ -2476,6 +2490,82 @@ function drupal_container(ContainerInterface $new_container = NULL) {
   return $container;
 }
 
+/**
+ * Returns the list of enabled modules.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   drupal_container()->get('module_handler')->getModuleList().
+ *
+ * @see \Drupal\Core\Extension\ModuleHandler::getModuleList()
+ */
+function module_list() {
+  $modules = array_keys(drupal_container()->get('module_handler')->getModuleList());
+  return array_combine($modules, $modules);
+}
+
+/**
+ * Determines which modules are implementing a hook.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   drupal_container()->get('module_handler')->getImplementations($hook).
+ *
+ * @see \Drupal\Core\Extension\ModuleHandler::getImplementations()
+ */
+function module_implements($hook) {
+  return drupal_container()->get('module_handler')->getImplementations($hook);
+}
+
+/**
+ * Invokes a hook in all enabled modules that implement it.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   drupal_container()->get('module_handler')->invokeAll($hook).
+ *
+ * @see \Drupal\Core\Extension\ModuleHandler::invokeAll()
+ */
+function module_invoke_all($hook) {
+  $args = func_get_args();
+  // Remove $hook from the arguments.
+  array_shift($args);
+  return drupal_container()->get('module_handler')->invokeAll($hook, $args);
+}
+
+/**
+ * Passes alterable variables to specific hook_TYPE_alter() implementations.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   drupal_container()->get('module_handler')->alter($hook).
+ *
+ * @see \Drupal\Core\Extension\ModuleHandler::alter()
+ */
+function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
+  return drupal_container()->get('module_handler')->alter($type, $data, $context1, $context2);
+}
+
+/**
+ * Determines whether a given module exists.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   drupal_container()->get('module_handler')->moduleExists($hook).
+ *
+ * @see \Drupal\Core\Extension\ModuleHandler::moduleExists()
+ */
+function module_exists($module) {
+  return drupal_container()->get('module_handler')->moduleExists($module);
+}
+
+/**
+ * Determines whether a module implements a hook.
+ *
+ * @deprecated as of Drupal 8.0. Use
+ *   drupal_container()->get('module_handler')->implementsHook($module, $hook).
+ *
+ * @see \Drupal\Core\Extension\ModuleHandler::implementsHook()
+ */
+function module_hook($module, $hook) {
+  return drupal_container()->get('module_handler')->implementsHook($module, $hook);
+}
+
 /**
  * Returns the state storage service.
  *
diff --git a/core/includes/common.inc b/core/includes/common.inc
index d97ef30107d9..0c16ac913a6b 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -4839,7 +4839,7 @@ function _drupal_bootstrap_code() {
   require_once DRUPAL_ROOT . '/core/includes/entity.inc';
 
   // Load all enabled modules
-  module_load_all();
+  drupal_container()->get('module_handler')->loadAll();
 
   // Make sure all stream wrappers are registered.
   file_get_stream_wrappers();
@@ -6438,7 +6438,7 @@ function drupal_flush_all_caches() {
 
   // Ensure that all modules that are currently supposed to be enabled are
   // actually loaded.
-  module_load_all();
+  drupal_container()->get('module_handler')->loadAll();
 
   // Update the list of bootstrap modules.
   // Allows developers to get new hook_boot() implementations registered without
@@ -6511,69 +6511,11 @@ function debug($data, $label = NULL, $print_r = FALSE) {
   trigger_error(trim($label ? "$label: $string" : $string));
 }
 
-/**
- * Parses a dependency for comparison by drupal_check_incompatibility().
- *
- * @param $dependency
- *   A dependency string, for example 'foo (>=8.x-4.5-beta5, 3.x)'.
- *
- * @return
- *   An associative array with three keys:
- *   - 'name' includes the name of the thing to depend on (e.g. 'foo').
- *   - 'original_version' contains the original version string (which can be
- *     used in the UI for reporting incompatibilities).
- *   - 'versions' is a list of associative arrays, each containing the keys
- *     'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<',
- *     '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'.
- *   Callers should pass this structure to drupal_check_incompatibility().
- *
- * @see drupal_check_incompatibility()
- */
-function drupal_parse_dependency($dependency) {
-  // We use named subpatterns and support every op that version_compare
-  // supports. Also, op is optional and defaults to equals.
-  $p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
-  // Core version is always optional: 8.x-2.x and 2.x is treated the same.
-  $p_core = '(?:' . preg_quote(DRUPAL_CORE_COMPATIBILITY) . '-)?';
-  $p_major = '(?P<major>\d+)';
-  // By setting the minor version to x, branches can be matched.
-  $p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
-  $value = array();
-  $parts = explode('(', $dependency, 2);
-  $value['name'] = trim($parts[0]);
-  if (isset($parts[1])) {
-    $value['original_version'] = ' (' . $parts[1];
-    foreach (explode(',', $parts[1]) as $version) {
-      if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) {
-        $op = !empty($matches['operation']) ? $matches['operation'] : '=';
-        if ($matches['minor'] == 'x') {
-          // Drupal considers "2.x" to mean any version that begins with
-          // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
-          // on the other hand, treats "x" as a string; so to
-          // version_compare(), "2.x" is considered less than 2.0. This
-          // means that >=2.x and <2.x are handled by version_compare()
-          // as we need, but > and <= are not.
-          if ($op == '>' || $op == '<=') {
-            $matches['major']++;
-          }
-          // Equivalence can be checked by adding two restrictions.
-          if ($op == '=' || $op == '==') {
-            $value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x');
-            $op = '>=';
-          }
-        }
-        $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']);
-      }
-    }
-  }
-  return $value;
-}
-
 /**
  * Checks whether a version is compatible with a given dependency.
  *
  * @param $v
- *   The parsed dependency structure from drupal_parse_dependency().
+ *   A parsed dependency structure e.g. from ModuleHandler::parseDependency().
  * @param $current_version
  *   The version to check against (like 4.2).
  *
@@ -6581,7 +6523,7 @@ function drupal_parse_dependency($dependency) {
  *   NULL if compatible, otherwise the original dependency version string that
  *   caused the incompatibility.
  *
- * @see drupal_parse_dependency()
+ * @see \Drupal\Core\Extension\ModuleHandler::parseDependency()
  */
 function drupal_check_incompatibility($v, $current_version) {
   if (!empty($v['versions'])) {
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 7f4b26b09805..20c690bef35f 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -338,6 +338,7 @@ function install_begin_request(&$install_state) {
     $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
       ->addArgument(new Reference('config.storage'))
       ->addArgument(new Reference('event_dispatcher'));
+
     // The install process cannot use the database lock backend since the database
     // is not fully up, so we use a null backend implementation during the
     // installation process. This will also speed up the installation process.
@@ -346,6 +347,10 @@ function install_begin_request(&$install_state) {
     // (as opposed to the cache backend) so we can afford having a null
     // implementation here.
     $container->register('lock', 'Drupal\Core\Lock\NullLockBackend');
+
+    // Register a module handler for managing enabled modules.
+    $container
+      ->register('module_handler', 'Drupal\Core\Extension\ModuleHandler');
     drupal_container($container);
   }
 
@@ -353,10 +358,13 @@ function install_begin_request(&$install_state) {
   drupal_language_initialize();
 
   require_once DRUPAL_ROOT . '/core/includes/ajax.inc';
-  // Override the module list with a minimal set of modules.
-  $module_list['system']['filename'] = 'core/modules/system/system.module';
-  module_list(NULL, $module_list);
-  drupal_load('module', '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');
@@ -1570,9 +1578,7 @@ function install_bootstrap_full(&$install_state) {
   // cache backend will be used again.
   unset($GLOBALS['conf']['cache_classes']['cache']);
   drupal_static_reset('cache');
-  // Clear the module list that was overriden earlier in the process.
-  // This will allow all freshly installed modules to be loaded.
-  module_list_reset();
+
   drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 }
 
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 9223b9c8d2d3..e9d666f0657b 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -424,15 +424,10 @@ function drupal_install_system() {
     ->set('enabled.system', 0)
     ->save();
 
-  // Clear out module list and hook implementation statics.
-  system_list_reset();
-  module_list_reset();
-  module_implements_reset();
-
-  // To ensure that the system module can be found by the plugin system, warm
-  // the module list cache.
-  // @todo Remove this in http://drupal.org/node/1798732.
-  module_list();
+  // Update the module list to include it.
+  drupal_container()->get('module_handler')->setModuleList(array('system' => $system_path . '/system.module'));
+  drupal_container()->get('module_handler')->resetImplementations();
+
   config_install_default_config('module', 'system');
 
   module_invoke('system', 'install');
diff --git a/core/includes/module.inc b/core/includes/module.inc
index f6d47b6f1647..83a3ba625ea2 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -9,114 +9,6 @@
 use Drupal\Component\Utility\NestedArray;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
-/**
- * Loads all enabled modules.
- *
- * @param bool $bootstrap
- *   Whether to load only the reduced set of modules loaded in "bootstrap mode"
- *   for cached pages. See bootstrap.inc. Pass NULL to only check the current
- *   status without loading of modules.
- * @param bool $reset
- *   (optional) Internal use only. Whether to reset the internal statically
- *   cached flag of whether modules have been loaded. If TRUE, all modules are
- *   (re)loaded in the same call. Used by the testing framework to override and
- *   persist a limited module list for the duration of a unit test (in which no
- *   module system exists).
- *
- * @return bool
- *   A Boolean indicating whether all modules have been loaded. This means all
- *   modules; the load status of bootstrap modules cannot be checked.
- */
-function module_load_all($bootstrap = FALSE, $reset = FALSE) {
-  static $has_run = FALSE;
-
-  if ($reset) {
-    $has_run = FALSE;
-  }
-
-  // Unless $boostrap is NULL, load the requested set of modules.
-  if (isset($bootstrap) && !$has_run) {
-    $type = $bootstrap ? 'bootstrap' : 'module_enabled';
-    foreach (module_list($type) as $module) {
-      drupal_load('module', $module);
-    }
-    // $has_run will be TRUE if $bootstrap is FALSE.
-    $has_run = !$bootstrap;
-  }
-  return $has_run;
-}
-
-/**
- * Returns a list of currently active modules.
- *
- * Acts as a wrapper around system_list(), returning either a list of all
- * enabled modules, or just modules needed for bootstrap.
- *
- * The returned module list is always based on system_list(). The only exception
- * to that is when a fixed list of modules has been passed in previously, in
- * which case system_list() is omitted and the fixed list is always returned in
- * subsequent calls until manually reverted via module_list_reset().
- *
- * @param string $type
- *   The type of list to return:
- *   - module_enabled: All enabled modules.
- *   - bootstrap: All enabled modules required for bootstrap.
- * @param array $fixed_list
- *   (optional) An array of module names to override the list of modules. This
- *   list will persist until the next call with a new $fixed_list passed in.
- *   Primarily intended for internal use (e.g., in install.php and update.php).
- *   Use module_list_reset() to undo the $fixed_list override.
- * @param bool $reset
- *   (optional) Whether to reset/remove the $fixed_list.
- *
- * @return array
- *   An associative array whose keys and values are the names of the modules in
- *   the list.
- *
- * @see module_list_reset()
- */
-function module_list($type = 'module_enabled', array $fixed_list = NULL, $reset = FALSE) {
-  // This static is only used for $fixed_list. It must not be a drupal_static(),
-  // since any call to drupal_static_reset() in unit tests would cause an
-  // attempt to retrieve the list of modules from the database (which does not
-  // exist).
-  static $module_list;
-
-  if ($reset) {
-    $module_list = NULL;
-    // Do nothing if no $type and no $fixed_list have been passed.
-    if (!isset($type) && !isset($fixed_list)) {
-      return;
-    }
-  }
-
-  // The list that will be be returned. Separate from $module_list in order
-  // to not duplicate the static cache of system_list().
-  $list = $module_list;
-
-  if (isset($fixed_list)) {
-    $module_list = array();
-    foreach ($fixed_list as $name => $module) {
-      system_register('module', $name, $module['filename']);
-      $module_list[$name] = $name;
-    }
-    $list = $module_list;
-  }
-  elseif (!isset($module_list)) {
-    $list = system_list($type);
-  }
-  return $list;
-}
-
-/**
- * Reverts an enforced fixed list of module_list().
- *
- * Subsequent calls to module_list() will no longer use a fixed list.
- */
-function module_list_reset() {
-  module_list(NULL, NULL, TRUE);
-}
-
 /**
  * Builds a list of bootstrap modules and enabled modules and themes.
  *
@@ -132,7 +24,6 @@ function module_list_reset() {
  *   For $type 'theme', the array values are objects representing the
  *   respective database row, with the 'info' property already unserialized.
  *
- * @see module_list()
  * @see list_themes()
  *
  * @todo There are too many layers/levels of caching involved for system_list()
@@ -142,121 +33,74 @@ function module_list_reset() {
  */
 function system_list($type) {
   $lists = &drupal_static(__FUNCTION__);
-
-  // For bootstrap modules, attempt to fetch the list from cache if possible.
-  // if not fetch only the required information to fire bootstrap hooks
-  // in case we are going to serve the page from cache.
-  if ($type == 'bootstrap') {
-    if (isset($lists['bootstrap'])) {
-      return $lists['bootstrap'];
-    }
-    if ($cached = cache('bootstrap')->get('bootstrap_modules')) {
-      $bootstrap_list = $cached->data;
-    }
-    else {
-      $bootstrap_list = state()->get('system.module.bootstrap') ?: array();
-      cache('bootstrap')->set('bootstrap_modules', $bootstrap_list);
-    }
-    // To avoid a separate database lookup for the filepath, prime the
-    // drupal_get_filename() static cache for bootstrap modules only.
-    // The rest is stored separately to keep the bootstrap module cache small.
-    foreach ($bootstrap_list as $name => $filename) {
-      system_register('module', $name, $filename);
-    }
-    // We only return the module names here since module_list() doesn't need
-    // the filename itself.
-    $lists['bootstrap'] = array_keys($bootstrap_list);
+  if ($cached = cache('bootstrap')->get('system_list')) {
+    $lists = $cached->data;
   }
-  // Otherwise build the list for enabled modules and themes.
-  elseif (!isset($lists['module_enabled'])) {
-    if ($cached = cache('bootstrap')->get('system_list')) {
-      $lists = $cached->data;
-    }
-    else {
-      $lists = array(
-        'module_enabled' => array(),
-        'theme' => array(),
-        'filepaths' => array(),
-      );
-      // The module name (rather than the filename) is used as the fallback
-      // weighting in order to guarantee consistent behavior across different
-      // Drupal installations, which might have modules installed in different
-      // locations in the file system. The ordering here must also be
-      // consistent with the one used in module_implements().
-      $enabled_modules = (array) config('system.module')->get('enabled');
-      $module_files = state()->get('system.module.files');
-      foreach ($enabled_modules as $name => $weight) {
-        // Build a list of all enabled modules.
-        $lists['module_enabled'][$name] = $name;
-        // Build a list of filenames so drupal_get_filename can use it.
+  else {
+    $lists = array(
+      'theme' => array(),
+      'filepaths' => array(),
+    );
+    // Build a list of themes.
+    $enabled_themes = (array) config('system.theme')->get('enabled');
+    // @todo Themes include all themes, including disabled/uninstalled. This
+    //   system.theme.data state will go away entirely as soon as themes have
+    //   a proper installation status.
+    // @see http://drupal.org/node/1067408
+    $theme_data = state()->get('system.theme.data');
+    if (empty($theme_data)) {
+      // @todo: system_list() may be called from _drupal_bootstrap_code(), in
+      // which case system.module is not loaded yet.
+      // Prevent a filesystem scan in drupal_load() and include it directly.
+      // @see http://drupal.org/node/1067408
+      require_once DRUPAL_ROOT . '/core/modules/system/system.module';
+      $theme_data = system_rebuild_theme_data();
+    }
+    foreach ($theme_data as $name => $theme) {
+      $theme->status = (int) isset($enabled_themes[$name]);
+      $lists['theme'][$name] = $theme;
+      // Build a list of filenames so drupal_get_filename can use it.
+      if (isset($enabled_themes[$name])) {
         $lists['filepaths'][] = array(
-          'type' => 'module',
+          'type' => 'theme',
           'name' => $name,
-          'filepath' => $module_files[$name],
+          'filepath' => $theme->filename,
         );
       }
-
-      // Build a list of themes.
-      $enabled_themes = (array) config('system.theme')->get('enabled');
-      // @todo Themes include all themes, including disabled/uninstalled. This
-      //   system.theme.data state will go away entirely as soon as themes have
-      //   a proper installation status.
-      // @see http://drupal.org/node/1067408
-      $theme_data = state()->get('system.theme.data');
-      if (empty($theme_data)) {
-        // @todo: system_list() may be called from _drupal_bootstrap_code() and
-        // module_load_all(), in which case system.module is not loaded yet.
-        // Prevent a filesystem scan in drupal_load() and include it directly.
-        // @see http://drupal.org/node/1067408
-        require_once DRUPAL_ROOT . '/core/modules/system/system.module';
-        $theme_data = system_rebuild_theme_data();
-      }
-      foreach ($theme_data as $name => $theme) {
-        $theme->status = (int) isset($enabled_themes[$name]);
-        $lists['theme'][$name] = $theme;
-        // Build a list of filenames so drupal_get_filename can use it.
-        if (isset($enabled_themes[$name])) {
-          $lists['filepaths'][] = array(
-            'type' => 'theme',
-            'name' => $name,
-            'filepath' => $theme->filename,
-          );
-        }
-      }
-      // @todo Move into list_themes(). Read info for a particular requested
-      //   theme from state instead.
-      foreach ($lists['theme'] as $key => $theme) {
-        if (!empty($theme->info['base theme'])) {
-          // Make a list of the theme's base themes.
-          require_once DRUPAL_ROOT . '/core/includes/theme.inc';
-          $lists['theme'][$key]->base_themes = drupal_find_base_themes($lists['theme'], $key);
-          // Don't proceed if there was a problem with the root base theme.
-          if (!current($lists['theme'][$key]->base_themes)) {
-            continue;
-          }
-          // Determine the root base theme.
-          $base_key = key($lists['theme'][$key]->base_themes);
-          // Add to the list of sub-themes for each of the theme's base themes.
-          foreach (array_keys($lists['theme'][$key]->base_themes) as $base_theme) {
-            $lists['theme'][$base_theme]->sub_themes[$key] = $lists['theme'][$key]->info['name'];
-          }
-          // Add the base theme's theme engine info.
-          $lists['theme'][$key]->info['engine'] = $lists['theme'][$base_key]->info['engine'];
+    }
+    // @todo Move into list_themes(). Read info for a particular requested
+    //   theme from state instead.
+    foreach ($lists['theme'] as $key => $theme) {
+      if (!empty($theme->info['base theme'])) {
+        // Make a list of the theme's base themes.
+        require_once DRUPAL_ROOT . '/core/includes/theme.inc';
+        $lists['theme'][$key]->base_themes = drupal_find_base_themes($lists['theme'], $key);
+        // Don't proceed if there was a problem with the root base theme.
+        if (!current($lists['theme'][$key]->base_themes)) {
+          continue;
         }
-        else {
-          // A plain theme is its own base theme.
-          $base_key = $key;
+        // Determine the root base theme.
+        $base_key = key($lists['theme'][$key]->base_themes);
+        // Add to the list of sub-themes for each of the theme's base themes.
+        foreach (array_keys($lists['theme'][$key]->base_themes) as $base_theme) {
+          $lists['theme'][$base_theme]->sub_themes[$key] = $lists['theme'][$key]->info['name'];
         }
-        // Set the theme engine prefix.
-        $lists['theme'][$key]->prefix = ($lists['theme'][$key]->info['engine'] == 'theme') ? $base_key : $lists['theme'][$key]->info['engine'];
+        // Add the base theme's theme engine info.
+        $lists['theme'][$key]->info['engine'] = $lists['theme'][$base_key]->info['engine'];
       }
-      cache('bootstrap')->set('system_list', $lists);
-    }
-    // To avoid a separate database lookup for the filepath, prime the
-    // drupal_get_filename() static cache with all enabled modules and themes.
-    foreach ($lists['filepaths'] as $item) {
-      system_register($item['type'], $item['name'], $item['filepath']);
+      else {
+        // A plain theme is its own base theme.
+        $base_key = $key;
+      }
+      // Set the theme engine prefix.
+      $lists['theme'][$key]->prefix = ($lists['theme'][$key]->info['engine'] == 'theme') ? $base_key : $lists['theme'][$key]->info['engine'];
     }
+    cache('bootstrap')->set('system_list', $lists);
+  }
+  // To avoid a separate database lookup for the filepath, prime the
+  // drupal_get_filename() static cache with all enabled modules and themes.
+  foreach ($lists['filepaths'] as $item) {
+    system_register($item['type'], $item['name'], $item['filepath']);
   }
 
   return $lists[$type];
@@ -269,7 +113,7 @@ function system_list_reset() {
   drupal_static_reset('system_list');
   drupal_static_reset('system_rebuild_module_data');
   drupal_static_reset('list_themes');
-  cache('bootstrap')->deleteMultiple(array('bootstrap_modules', 'system_list'));
+  cache('bootstrap')->delete('system_list');
   cache()->delete('system_info');
   // Remove last known theme data state.
   // This causes system_list() to call system_rebuild_theme_data() on its next
@@ -297,53 +141,6 @@ function system_register($type, $name, $uri) {
   drupal_classloader_register($name, dirname($uri));
 }
 
-/**
- * Determines which modules require and are required by each module.
- *
- * @param $files
- *   The array of filesystem objects used to rebuild the cache.
- *
- * @return
- *   The same array with the new keys for each module:
- *   - requires: An array with the keys being the modules that this module
- *     requires.
- *   - required_by: An array with the keys being the modules that will not work
- *     without this module.
- */
-function _module_build_dependencies($files) {
-  foreach ($files as $filename => $file) {
-    $graph[$file->name]['edges'] = array();
-    if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
-      foreach ($file->info['dependencies'] as $dependency) {
-        $dependency_data = drupal_parse_dependency($dependency);
-        $graph[$file->name]['edges'][$dependency_data['name']] = $dependency_data;
-      }
-    }
-  }
-  $graph_object = new Graph($graph);
-  $graph = $graph_object->searchAndSort();
-  foreach ($graph as $module => $data) {
-    $files[$module]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : array();
-    $files[$module]->requires = isset($data['paths']) ? $data['paths'] : array();
-    $files[$module]->sort = $data['weight'];
-  }
-  return $files;
-}
-
-/**
- * Determines whether a given module exists.
- *
- * @param $module
- *   The name of the module (without the .module extension).
- *
- * @return
- *   TRUE if the module is both installed and enabled.
- */
-function module_exists($module) {
-  $list = module_list();
-  return isset($list[$module]);
-}
-
 /**
  * Loads a module's installation hooks.
  *
@@ -386,6 +183,11 @@ function module_load_install($module) {
  *
  * @return
  *   The name of the included file, if successful; FALSE otherwise.
+ *
+ * @todo The module_handler service has a loadInclude() method which performs
+ *   this same task but only for enabled modules. Figure out a way to move this
+ *   functionality entirely into the module_handler while keeping the ability to
+ *   load the files of disabled modules.
  */
 function module_load_include($type, $module, $name = NULL) {
   if (!isset($name)) {
@@ -402,15 +204,6 @@ function module_load_include($type, $module, $name = NULL) {
   return FALSE;
 }
 
-/**
- * Loads an include file for each enabled module.
- */
-function module_load_all_includes($type, $name = NULL) {
-  $modules = module_list();
-  foreach ($modules as $module) {
-    module_load_include($type, $module, $name);
-  }
-}
 
 /**
  * Enables or installs a given list of modules.
@@ -494,9 +287,12 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
   $schema_store = drupal_container()->get('keyvalue')->get('system.schema');
   $module_config = config('system.module');
   $disabled_config = config('system.module.disabled');
-  $module_filenames = drupal_container()->getParameter('container.modules');
+  $module_handler = drupal_container()->get('module_handler');
   foreach ($module_list as $module) {
     // Only process modules that are not already enabled.
+    // A module is only enabled if it is configured as enabled. Custom or
+    // overridden module handlers might contain the module already, which means
+    // that it might be loaded, but not necessarily installed or enabled.
     $enabled = $module_config->get("enabled.$module") !== NULL;
     if (!$enabled) {
       $weight = $disabled_config->get($module);
@@ -510,21 +306,59 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
       $disabled_config
         ->clear($module)
         ->save();
-      // Load the module's code.
-      drupal_load('module', $module);
+
+      // Prepare the new module list, sorted by weight, including filenames.
+      // This list is used for both the ModuleHandler and DrupalKernel. It needs
+      // to be kept in sync between both. A DrupalKernel reboot or rebuild will
+      // automatically re-instantiate a new ModuleHandler that uses the new
+      // module list of the kernel. However, DrupalKernel does not cause any
+      // modules to be loaded.
+      // Furthermore, the currently active (fixed) module list can be different
+      // from the configured list of enabled modules. For all active modules not
+      // contained in the configured enabled modules, we assume a weight of 0.
+      $current_module_filenames = $module_handler->getModuleList();
+      $current_modules = array_fill_keys(array_keys($current_module_filenames), 0);
+      $current_modules = module_config_sort(array_merge($current_modules, $module_config->get('enabled')));
+      $module_filenames = array();
+      foreach ($current_modules as $name => $weight) {
+        if (isset($current_module_filenames[$name])) {
+          $filename = $current_module_filenames[$name];
+        }
+        else {
+          $filename = drupal_get_filename('module', $name);
+        }
+        $module_filenames[$name] = $filename;
+      }
+
+      // Update the module handler in order to load the module's code.
+      // This allows the module to participate in hooks and its existence to be
+      // discovered by other modules.
+      // The current ModuleHandler instance is obsolete with the kernel rebuild
+      // below.
+      $module_handler->setModuleList($module_filenames);
+      $module_handler->load($module);
       module_load_install($module);
 
-      // Refresh the module list to include it.
+      // Reset the the hook implementations cache.
+      $module_handler->resetImplementations();
+
+      // Flush theme info caches, since (testing) modules can implement
+      // hook_system_theme_info() to register additional themes.
       system_list_reset();
-      module_implements_reset();
+      // Refresh the list of modules that implement bootstrap hooks.
+      // @see bootstrap_hooks()
       _system_update_bootstrap_status();
-      $module_filenames[$module] = drupal_get_filename('module', $module);
+
       // Update the kernel to include it.
-      // @todo The if statement is here because install_begin_request() creates
-      //   a container without a kernel. It probably shouldn't.
+      // This reboots the kernel to register the module's bundle and its
+      // services in the service container. The $module_filenames argument is
+      // taken over as %container.modules% parameter, which is passed to a fresh
+      // ModuleHandler instance upon first retrieval.
+      // @todo install_begin_request() creates a container without a kernel.
       if ($kernel = drupal_container()->get('kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
-        $kernel->updateModules(module_list(), $module_filenames);
+        $kernel->updateModules($module_filenames, $module_filenames);
       }
+
       // Refresh the schema to include it.
       drupal_get_schema(NULL, TRUE);
       // Update the theme registry to include it.
@@ -632,32 +466,58 @@ function module_disable($module_list, $disable_dependents = TRUE) {
 
   $module_config = config('system.module');
   $disabled_config = config('system.module.disabled');
+  $module_handler = drupal_container()->get('module_handler');
   foreach ($module_list as $module) {
-    if (module_exists($module)) {
+    // Only process modules that are enabled.
+    // A module is only enabled if it is configured as enabled. Custom or
+    // overridden module handlers might contain the module already, which means
+    // that it might be loaded, but not necessarily installed or enabled.
+    $enabled = $module_config->get("enabled.$module") !== NULL;
+    if ($enabled) {
       module_load_install($module);
       module_invoke($module, 'disable');
+
       $disabled_config
         ->set($module, $module_config->get($module))
         ->save();
       $module_config
         ->clear("enabled.$module")
         ->save();
+
+      // Update the module handler to remove the module.
+      // The current ModuleHandler instance is obsolete with the kernel rebuild
+      // below.
+      $module_filenames = $module_handler->getModuleList();
+      unset($module_filenames[$module]);
+      $module_handler->setModuleList($module_filenames);
+
+      // Record the fact that it was disabled.
       $invoke_modules[] = $module;
       watchdog('system', '%module module disabled.', array('%module' => $module), WATCHDOG_INFO);
     }
   }
 
   if (!empty($invoke_modules)) {
+    // @todo Most of the following should happen in above loop already.
     // Refresh the module list to exclude the disabled modules.
+    $module_handler->resetImplementations();
+
+    // Refresh the system list to exclude the disabled modules.
+    // @todo Only needed to rebuild theme info.
+    // @see system_list_reset()
     system_list_reset();
-    module_implements_reset();
+
     entity_info_cache_clear();
+
     // Invoke hook_modules_disabled before disabling modules,
     // so we can still call module hooks to get information.
     module_invoke_all('modules_disabled', $invoke_modules);
     _system_update_bootstrap_status();
+
     // Update the kernel to exclude the disabled modules.
-    drupal_container()->get('kernel')->updateModules(module_list());
+    $enabled = $module_handler->getModuleList();
+    drupal_container()->get('kernel')->updateModules($enabled, $enabled);
+
     // Update the theme registry to remove the newly-disabled module.
     drupal_theme_rebuild();
   }
@@ -765,200 +625,6 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE)
  * See also @link themeable the themeable group page. @endlink
  */
 
-/**
- * Determines whether a module implements a hook.
- *
- * @param $module
- *   The name of the module (without the .module extension).
- * @param $hook
- *   The name of the hook (e.g. "help" or "menu").
- *
- * @return
- *   TRUE if the module is both installed and enabled, and the hook is
- *   implemented in that module.
- */
-function module_hook($module, $hook) {
-  $function = $module . '_' . $hook;
-  if (function_exists($function)) {
-    return TRUE;
-  }
-  // If the hook implementation does not exist, check whether it may live in an
-  // optional include file registered via hook_hook_info().
-  $hook_info = module_hook_info();
-  if (isset($hook_info[$hook]['group'])) {
-    module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
-    if (function_exists($function)) {
-      return TRUE;
-    }
-  }
-  return FALSE;
-}
-
-/**
- * Determines which modules are implementing a hook.
- *
- * @param $hook
- *   The name of the hook (e.g. "help" or "menu").
- *
- * @return
- *   An array with the names of the modules which are implementing this hook.
- *
- * @see module_implements_write_cache()
- */
-function module_implements($hook) {
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['implementations'] = &drupal_static(__FUNCTION__);
-  }
-  $implementations = &$drupal_static_fast['implementations'];
-
-  // Fetch implementations from cache.
-  if (empty($implementations)) {
-    $implementations = cache('bootstrap')->get('module_implements');
-    if ($implementations === FALSE) {
-      $implementations = array();
-    }
-    else {
-      $implementations = $implementations->data;
-    }
-  }
-
-  if (!isset($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.
-    $implementations['#write_cache'] = TRUE;
-    $hook_info = module_hook_info();
-    $implementations[$hook] = array();
-    foreach (module_list() as $module) {
-      $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)) {
-        $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') {
-      drupal_alter('module_implements', $implementations[$hook], $hook);
-    }
-  }
-  else {
-    foreach ($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($implementations[$hook][$module]);
-        $implementations['#write_cache'] = TRUE;
-      }
-    }
-  }
-
-  return array_keys($implementations[$hook]);
-}
-
-/**
- * Regenerates the stored list of hook implementations.
- */
-function module_implements_reset() {
-  // 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.
-  drupal_static_reset('module_implements');
-  cache('bootstrap')->set('module_implements', array());
-  drupal_static_reset('module_hook_info');
-  drupal_static_reset('drupal_alter');
-  cache('bootstrap')->delete('hook_info');
-}
-
-/**
- * Retrieves a list of hooks that are declared through hook_hook_info().
- *
- * @return
- *   An associative array whose keys are hook names and whose values are an
- *   associative array containing a group name. The structure of the array
- *   is the same as the return value of hook_hook_info().
- *
- * @see hook_hook_info()
- */
-function module_hook_info() {
-  // 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 (!module_load_all(NULL)) {
-    return array();
-  }
-  $hook_info = &drupal_static(__FUNCTION__);
-
-  if (!isset($hook_info)) {
-    $hook_info = array();
-    $cache = cache('bootstrap')->get('hook_info');
-    if ($cache === FALSE) {
-      // Rebuild the cache and save it.
-      // We can't use module_invoke_all() here or it would cause an infinite
-      // loop.
-      foreach (module_list() as $module) {
-        $function = $module . '_hook_info';
-        if (function_exists($function)) {
-          $result = $function();
-          if (isset($result) && is_array($result)) {
-            $hook_info = NestedArray::mergeDeep($hook_info, $result);
-          }
-        }
-      }
-      // We can't use drupal_alter() for the same reason as above.
-      foreach (module_list() as $module) {
-        $function = $module . '_hook_info_alter';
-        if (function_exists($function)) {
-          $function($hook_info);
-        }
-      }
-      cache('bootstrap')->set('hook_info', $hook_info);
-    }
-    else {
-      $hook_info = $cache->data;
-    }
-  }
-
-  return $hook_info;
-}
-
-/**
- * Writes the hook implementation cache.
- *
- * @see module_implements()
- */
-function module_implements_write_cache() {
-  $implementations = &drupal_static('module_implements');
-  // Check whether we need to write the 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 (isset($implementations['#write_cache']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')) {
-    unset($implementations['#write_cache']);
-    cache('bootstrap')->set('module_implements', $implementations);
-  }
-}
-
 /**
  * Invokes a hook in a particular module.
  *
@@ -981,39 +647,6 @@ function module_invoke($module, $hook) {
   }
 }
 
-/**
- * Invokes a hook in all enabled modules that implement it.
- *
- * @param $hook
- *   The name of the hook to invoke.
- * @param ...
- *   Arguments to pass to the hook.
- *
- * @return
- *   An array of return values of the hook implementations. If modules return
- *   arrays from their implementations, those are merged into one array.
- */
-function module_invoke_all($hook) {
-  $args = func_get_args();
-  // Remove $hook from the arguments.
-  unset($args[0]);
-  $return = array();
-  foreach (module_implements($hook) as $module) {
-    $function = $module . '_' . $hook;
-    if (function_exists($function)) {
-      $result = call_user_func_array($function, $args);
-      if (isset($result) && is_array($result)) {
-        $return = NestedArray::mergeDeep($return, $result);
-      }
-      elseif (isset($result)) {
-        $return[] = $result;
-      }
-    }
-  }
-
-  return $return;
-}
-
 /**
  * @} End of "defgroup hooks".
  */
@@ -1038,172 +671,6 @@ function drupal_required_modules() {
   return $required;
 }
 
-/**
- * Passes alterable variables to specific hook_TYPE_alter() implementations.
- *
- * This dispatch function hands off the passed-in variables to type-specific
- * hook_TYPE_alter() implementations in modules. It ensures a consistent
- * interface for all altering operations.
- *
- * A maximum of 2 alterable arguments is supported. In case more arguments need
- * to be passed and alterable, modules provide additional variables assigned by
- * reference in the last $context argument:
- * @code
- *   $context = array(
- *     'alterable' => &$alterable,
- *     'unalterable' => $unalterable,
- *     'foo' => 'bar',
- *   );
- *   drupal_alter('mymodule_data', $alterable1, $alterable2, $context);
- * @endcode
- *
- * Note that objects are always passed by reference in PHP5. If it is absolutely
- * required that no implementation alters a passed object in $context, then an
- * object needs to be cloned:
- * @code
- *   $context = array(
- *     'unalterable_object' => clone $object,
- *   );
- *   drupal_alter('mymodule_data', $data, $context);
- * @endcode
- *
- * @param $type
- *   A string describing the type of the alterable $data. 'form', 'links',
- *   'node_content', and so on are several examples. Alternatively can be an
- *   array, in which case hook_TYPE_alter() is invoked for each value in the
- *   array, ordered first by module, and then for each module, in the order of
- *   values in $type. For example, when Form API is using drupal_alter() to
- *   execute both hook_form_alter() and hook_form_FORM_ID_alter()
- *   implementations, it passes array('form', 'form_' . $form_id) for $type.
- * @param $data
- *   The variable that will be passed to hook_TYPE_alter() implementations to be
- *   altered. The type of this variable depends on the value of the $type
- *   argument. For example, when altering a 'form', $data will be a structured
- *   array. When altering a 'profile', $data will be an object.
- * @param $context1
- *   (optional) An additional variable that is passed by reference.
- * @param $context2
- *   (optional) An additional variable that is passed by reference. If more
- *   context needs to be provided to implementations, then this should be an
- *   associative array as described above.
- */
-function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['functions'] = &drupal_static(__FUNCTION__);
-  }
-  $functions = &$drupal_static_fast['functions'];
-
-  // Most of the time, $type is passed as a string, so for performance,
-  // normalize it to that. When passed as an array, usually the first item in
-  // the array is a generic type, and additional items in the array are more
-  // specific variants of it, as in the case of array('form', 'form_FORM_ID').
-  if (is_array($type)) {
-    $cid = implode(',', $type);
-    $extra_types = $type;
-    $type = array_shift($extra_types);
-    // Allow if statements in this function to use the faster isset() rather
-    // than !empty() both when $type is passed as a string, or as an array with
-    // one item.
-    if (empty($extra_types)) {
-      unset($extra_types);
-    }
-  }
-  else {
-    $cid = $type;
-  }
-
-  // Some alter hooks are invoked many times per page request, so statically
-  // cache the list of functions to call, and on subsequent calls, iterate
-  // through them quickly.
-  if (!isset($functions[$cid])) {
-    $functions[$cid] = array();
-    $hook = $type . '_alter';
-    $modules = module_implements($hook);
-    if (!isset($extra_types)) {
-      // For the more common case of a single hook, we do not need to call
-      // function_exists(), since module_implements() returns only modules with
-      // implementations.
-      foreach ($modules as $module) {
-        $functions[$cid][] = $module . '_' . $hook;
-      }
-    }
-    else {
-      // For multiple hooks, we need $modules to contain every module that
-      // implements at least one of them.
-      $extra_modules = array();
-      foreach ($extra_types as $extra_type) {
-        $extra_modules = array_merge($extra_modules, module_implements($extra_type . '_alter'));
-      }
-      // If any modules implement one of the extra hooks that do not implement
-      // the primary hook, we need to add them to the $modules array in their
-      // appropriate order. module_implements() can only return ordered
-      // implementations of a single hook. To get the ordered implementations
-      // of multiple hooks, we mimic the module_implements() logic of first
-      // ordering by module_list(), and then calling
-      // drupal_alter('module_implements').
-      if (array_diff($extra_modules, $modules)) {
-        // Merge the arrays and order by module_list().
-        $modules = array_intersect(module_list(), array_merge($modules, $extra_modules));
-        // Since module_implements() already took care of loading the necessary
-        // include files, we can safely pass FALSE for the array values.
-        $implementations = array_fill_keys($modules, FALSE);
-        // Let modules adjust the order solely based on the primary hook. This
-        // ensures the same module order regardless of whether this if block
-        // runs. Calling drupal_alter() recursively in this way does not result
-        // in an infinite loop, because this call is for a single $type, so we
-        // won't end up in this code block again.
-        drupal_alter('module_implements', $implementations, $hook);
-        $modules = array_keys($implementations);
-      }
-      foreach ($modules as $module) {
-        // Since $modules is a merged array, for any given module, we do not
-        // know whether it has any particular implementation, so we need a
-        // function_exists().
-        $function = $module . '_' . $hook;
-        if (function_exists($function)) {
-          $functions[$cid][] = $function;
-        }
-        foreach ($extra_types as $extra_type) {
-          $function = $module . '_' . $extra_type . '_alter';
-          if (function_exists($function)) {
-            $functions[$cid][] = $function;
-          }
-        }
-      }
-    }
-    // Allow the theme to alter variables after the theme system has been
-    // initialized.
-    global $theme, $base_theme_info;
-    if (isset($theme)) {
-      $theme_keys = array();
-      foreach ($base_theme_info as $base) {
-        $theme_keys[] = $base->name;
-      }
-      $theme_keys[] = $theme;
-      foreach ($theme_keys as $theme_key) {
-        $function = $theme_key . '_' . $hook;
-        if (function_exists($function)) {
-          $functions[$cid][] = $function;
-        }
-        if (isset($extra_types)) {
-          foreach ($extra_types as $extra_type) {
-            $function = $theme_key . '_' . $extra_type . '_alter';
-            if (function_exists($function)) {
-              $functions[$cid][] = $function;
-            }
-          }
-        }
-      }
-    }
-  }
-
-  foreach ($functions[$cid] as $function) {
-    $function($data, $context1, $context2);
-  }
-}
-
 /**
  * Sets weight of a particular module.
  *
@@ -1222,6 +689,19 @@ function module_set_weight($module, $weight) {
       ->set("enabled.$module", $weight)
       ->set('enabled', module_config_sort($module_config->get('enabled')))
       ->save();
+
+    // Prepare the new module list, sorted by weight, including filenames.
+    // @see module_enable()
+    $module_handler = drupal_container()->get('module_handler');
+    $current_module_filenames = $module_handler->getModuleList();
+    $current_modules = array_fill_keys(array_keys($current_module_filenames), 0);
+    $current_modules = module_config_sort(array_merge($current_modules, $module_config->get('enabled')));
+    $module_filenames = array();
+    foreach ($current_modules as $name => $weight) {
+      $module_filenames[$name] = $current_module_filenames[$name];
+    }
+    // Update the module list in the extension handler.
+    $module_handler->setModuleList($module_filenames);
     return;
   }
   $disabled_config = config('system.module.disabled');
diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index b37014501f76..e59992370b2c 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -77,16 +77,7 @@ function drupal_get_complete_schema($rebuild = FALSE) {
     else {
       $schema = array();
       // Load the .install files to get hook_schema.
-      // On some databases this function may be called before bootstrap has
-      // been completed, so we force the functions we need to load just in case.
-      if (function_exists('module_load_all_includes')) {
-        // This function can be called very early in the bootstrap process, so
-        // we force the system_list() static cache to be refreshed to ensure
-        // that it contains the complete list of modules before we go on to call
-        // module_load_all_includes().
-        system_list_reset();
-        module_load_all_includes('install');
-      }
+      drupal_container()->get('module_handler')->loadAllIncludes('install');
 
       require_once DRUPAL_ROOT . '/core/includes/common.inc';
       // Invoke hook_schema for all modules.
@@ -130,8 +121,7 @@ function drupal_get_schema_versions($module) {
   $updates = &drupal_static(__FUNCTION__, NULL);
   if (!isset($updates[$module])) {
     $updates = array();
-
-    foreach (module_list() as $loaded_module) {
+    foreach (drupal_container()->get('module_handler')->getModuleList() as $loaded_module => $filename) {
       $updates[$loaded_module] = array();
     }
 
@@ -341,7 +331,9 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU
  *   An array of fields.
  */
 function drupal_schema_fields_sql($table, $prefix = NULL) {
-  $schema = drupal_get_schema($table);
+  if (!$schema = drupal_get_schema($table)) {
+    return array();
+  }
   $fields = array_keys($schema['fields']);
   if ($prefix) {
     $columns = array();
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index bab2af5228d5..5dd028b914e3 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -368,14 +368,14 @@ function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL,
       $registry = _theme_build_registry($theme, $base_theme, $theme_engine);
       // Only persist this registry if all modules are loaded. This assures a
       // complete set of theme hooks.
-      if (module_load_all(NULL)) {
+      if (drupal_container()->get('module_handler')->isLoaded()) {
         _theme_save_registry($theme, $registry);
       }
     }
     return $registry;
   }
   else {
-    return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache', array('theme_registry' => TRUE));
+    return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache', array('theme_registry' => TRUE), drupal_container()->get('module_handler')->isLoaded());
   }
 }
 
@@ -441,7 +441,6 @@ function drupal_theme_rebuild() {
  *   themes/bartik.
  *
  * @see theme()
- * @see _theme_process_registry()
  * @see hook_theme()
  * @see list_themes()
  */
@@ -548,7 +547,7 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
             // Add all modules so they can intervene with their own variable
             // processors. This allows them to provide variable processors even
             // if they are not the owner of the current hook.
-            $prefixes += module_list();
+            $prefixes = array_merge($prefixes, array_keys(drupal_container()->get('module_handler')->getModuleList()));
           }
           elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
             // Theme engines get an extra set that come before the normally
@@ -644,7 +643,7 @@ function _theme_build_registry($theme, $base_theme, $theme_engine) {
       _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
     }
     // Only cache this registry if all modules are loaded.
-    if (module_load_all(NULL)) {
+    if (drupal_container()->get('module_handler')->isLoaded()) {
       cache()->set("theme_registry:build:modules", $cache, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE));
     }
   }
@@ -959,7 +958,7 @@ function theme($hook, $variables = array()) {
   // If called before all modules are loaded, we do not necessarily have a full
   // theme registry to work with, and therefore cannot process the theme
   // request properly. See also _theme_load_registry().
-  if (!module_load_all(NULL) && !defined('MAINTENANCE_MODE')) {
+  if (!drupal_container()->get('module_handler')->isLoaded() && !defined('MAINTENANCE_MODE')) {
     throw new Exception(t('theme() may not be called until all modules are loaded.'));
   }
 
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index ba8866133fd4..0b426e210a58 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -55,9 +55,10 @@ function _drupal_maintenance_theme() {
 
   // Ensure that system.module is loaded.
   if (!function_exists('_system_rebuild_theme_data')) {
-    $module_list['system']['filename'] = 'core/modules/system/system.module';
-    module_list(NULL, $module_list);
-    drupal_load('module', 'system');
+    $module_list['system'] = 'core/modules/system/system.module';
+    $module_handler = drupal_container()->get('module_handler');
+    $module_handler->setModuleList($module_list);
+    $module_handler->load('system');
   }
 
   $themes = list_themes();
diff --git a/core/includes/update.inc b/core/includes/update.inc
index cdcae638258a..508e01babfe2 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -11,6 +11,7 @@
 use Drupal\Component\Graph\Graph;
 use Drupal\Core\Config\FileStorage;
 use Drupal\Core\Config\ConfigException;
+use Drupal\Core\DrupalKernel;
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Component\Utility\NestedArray;
 
@@ -93,6 +94,19 @@ function update_prepare_d8_bootstrap() {
   // Bootstrap to configuration.
   drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
 
+  // During the bootstrap to DRUPAL_BOOTSTRAP_PAGE_CACHE, code will try to read
+  // the cache but the cache tables are not compatible yet. Use the Null backend
+  // by default to avoid exceptions.
+  $GLOBALS['conf']['cache_classes'] = array('cache' => 'Drupal\Core\Cache\NullBackend');
+
+  // Enable UpdateBundle service overrides.
+  $GLOBALS['conf']['container_bundles'][] = 'Drupal\Core\DependencyInjection\UpdateBundle';
+
+  // Bootstrap the kernel.
+  // Do not attempt to dump and write it.
+  $kernel = new DrupalKernel('update', FALSE, drupal_classloader(), FALSE);
+  $kernel->boot();
+
   // Check whether settings.php needs to be rewritten.
   $settings_exist = !empty($GLOBALS['config_directories']);
 
@@ -125,9 +139,9 @@ function update_prepare_d8_bootstrap() {
 
     include_once DRUPAL_ROOT . '/core/includes/module.inc';
     include_once DRUPAL_ROOT . '/core/includes/cache.inc';
-    $module_list['system']['filename'] = 'core/modules/system/system.module';
-    module_list(NULL, $module_list);
-    require_once DRUPAL_ROOT . '/' . $module_list['system']['filename'];
+    $module_handler = drupal_container()->get('module_handler');
+    $module_handler->setModuleList(array('system' => 'core/modules/system/system.module'));
+    $module_handler->load('system');
     // Ensure the configuration directories exist and are writable, or create
     // them. If the directories have not been specified in settings.php and
     // created manually already, and either directory cannot be created by the
@@ -135,10 +149,7 @@ function update_prepare_d8_bootstrap() {
     drupal_install_config_directories();
   }
 
-  // Bootstrap the database. During this, the DRUPAL_BOOTSTRAP_PAGE_CACHE will
-  // try to read the cache but the cache tables might not be Drupal 8
-  // compatible yet. Use the null backend by default to avoid exceptions.
-  $GLOBALS['conf']['cache_classes'] = array('cache' => 'Drupal\Core\Cache\NullBackend');
+  // Bootstrap the database.
   drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);
 
   // If the site has not updated to Drupal 8 yet, check to make sure that it is
@@ -353,8 +364,8 @@ function update_prepare_d8_bootstrap() {
 
       // Populate a fixed module list (again, why did it get lost?) to avoid
       // errors due to the drupal_alter() in _system_rebuild_module_data().
-      $module_list['system']['filename'] = 'core/modules/system/system.module';
-      module_list(NULL, $module_list);
+      $module_list['system'] = 'core/modules/system/system.module';
+      drupal_container()->get('module_handler')->setModuleList($module_list);
       $module_data = _system_rebuild_module_data();
 
       // Migrate each extension into configuration, varying by the extension's
@@ -378,7 +389,13 @@ function update_prepare_d8_bootstrap() {
         }
         $schema_store->set($record->name, $record->schema_version);
       }
-      $module_config->set('enabled', module_config_sort($module_config->get('enabled')))->save();
+      $sorted_modules = module_config_sort($module_config->get('enabled'));
+      $module_config->set('enabled', $sorted_modules)->save();
+      $sorted_with_filenames = array();
+      foreach (array_keys($sorted_modules) as $m) {
+        $sorted_with_filenames[$m] = drupal_get_filename('module', $m);
+      }
+      drupal_container()->get('module_handler')->setModuleList($sorted_with_filenames);
       $disabled_modules->save();
       $theme_config->save();
       $disabled_themes->save();
@@ -388,8 +405,6 @@ function update_prepare_d8_bootstrap() {
       update_prepare_stored_includes();
       // Update the environment for the language bootstrap if needed.
       update_prepare_d8_language();
-      // Prime the classloader.
-      system_list('module_enabled');
 
       // Change language column to langcode in url_alias.
       if (db_table_exists('url_alias') && db_field_exists('url_alias', 'language')) {
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 207670758dd7..e70b1fc62c6b 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -77,6 +77,12 @@ public function build(ContainerBuilder $container) {
       ->setFactoryClass('Drupal\Component\Utility\Settings')
       ->setFactoryMethod('getSingleton');
 
+    // Register the State k/v store as a service.
+    $container->register('state', 'Drupal\Core\KeyValueStore\KeyValueStoreInterface')
+      ->setFactoryService(new Reference('keyvalue'))
+      ->setFactoryMethod('get')
+      ->addArgument('state');
+
     // Register the Queue factory.
     $container
       ->register('queue', 'Drupal\Core\Queue\QueueFactory')
@@ -114,6 +120,20 @@ public function build(ContainerBuilder $container) {
       ->addArgument(new Reference('service_container'));
     $container->register('controller_resolver', 'Drupal\Core\ControllerResolver')
       ->addArgument(new Reference('service_container'));
+
+    $container
+      ->register('cache.cache', 'Drupal\Core\Cache\CacheBackendInterface')
+      ->setFactoryClass('Drupal\Core\Cache\CacheFactory')
+      ->setFactoryMethod('get')
+      ->addArgument('cache');
+    $container
+      ->register('cache.bootstrap', 'Drupal\Core\Cache\CacheBackendInterface')
+      ->setFactoryClass('Drupal\Core\Cache\CacheFactory')
+      ->setFactoryMethod('get')
+      ->addArgument('bootstrap');
+
+    $this->registerModuleHandler($container);
+
     $container->register('http_kernel', 'Drupal\Core\HttpKernel')
       ->addArgument(new Reference('event_dispatcher'))
       ->addArgument(new Reference('service_container'))
@@ -144,7 +164,8 @@ public function build(ContainerBuilder $container) {
     $container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
       ->addArgument(new Reference('router.dumper'))
       ->addArgument(new Reference('lock'))
-      ->addArgument(new Reference('event_dispatcher'));
+      ->addArgument(new Reference('event_dispatcher'))
+      ->addArgument(new Reference('module_handler'));
 
     $container
       ->register('cache.path', 'Drupal\Core\Cache\CacheBackendInterface')
@@ -209,6 +230,7 @@ public function build(ContainerBuilder $container) {
       ->setScope('request')
       ->addTag('event_subscriber');
     $container->register('request_close_subscriber', 'Drupal\Core\EventSubscriber\RequestCloseSubscriber')
+      ->addArgument(new Reference('module_handler'))
       ->addTag('event_subscriber');
     $container->register('config_global_override_subscriber', 'Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber')
       ->addTag('event_subscriber');
@@ -247,6 +269,25 @@ public function build(ContainerBuilder $container) {
     $container->addCompilerPass(new RegisterAccessChecksPass());
   }
 
+  /**
+   * 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\Extension\ModuleHandler')
+        ->addArgument('%container.modules%');
+    }
+    else {
+      $container->register('module_handler', 'Drupal\Core\Extension\CachedModuleHandler')
+        ->addArgument('%container.modules%')
+        ->addArgument(new Reference('state'))
+        ->addArgument(new Reference('cache.bootstrap'));
+    }
+  }
+
   /**
    * Registers the various services for the routing system.
    *
diff --git a/core/lib/Drupal/Core/DependencyInjection/UpdateBundle.php b/core/lib/Drupal/Core/DependencyInjection/UpdateBundle.php
new file mode 100644
index 000000000000..539affd1bcdd
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/UpdateBundle.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\DependencyInjection\UpdateBundle.
+ */
+
+namespace Drupal\Core\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * Bundle class for update.php service overrides.
+ *
+ * This bundle is manually added by update.php via $conf['container_bundles']
+ * and required to prevent various services from trying to retrieve data from
+ * storages that do not exist yet.
+ */
+class UpdateBundle extends Bundle {
+
+  /**
+   * Implements \Symfony\Component\HttpKernel\Bundle\BundleInterface::build().
+   */
+  public function build(ContainerBuilder $container) {
+    // Disable the Lock service.
+    $container
+      ->register('lock', 'Drupal\Core\Lock\NullLockBackend');
+
+    // Prevent config from accessing {cache_config}.
+    // @see $conf['cache_classes'], update_prepare_d8_bootstrap()
+    $container
+      ->register('config.storage', 'Drupal\Core\Config\FileStorage')
+      ->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index ffa5ef4a3938..d56f5037c86d 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -223,6 +223,9 @@ protected function moduleData($module) {
 
   /**
    * Implements Drupal\Core\DrupalKernelInterface::updateModules().
+   *
+   * @todo Remove obsolete $module_list parameter. Only $module_filenames is
+   *   needed.
    */
   public function updateModules(array $module_list, array $module_filenames = array()) {
     $this->newModuleList = $module_list;
diff --git a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
index 8d5f87a6ac46..eb295bdb8261 100644
--- a/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\EventSubscriber;
 
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\CachedModuleHandlerInterface;
 use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\Component\HttpKernel\Event\PostResponseEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -16,6 +18,18 @@
  */
 class RequestCloseSubscriber implements EventSubscriberInterface {
 
+  /**
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructor.
+   */
+  function __construct(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+
   /**
    * Performs end of request tasks.
    *
@@ -28,8 +42,15 @@ class RequestCloseSubscriber implements EventSubscriberInterface {
    *   The Event to process.
    */
   public function onTerminate(PostResponseEvent $event) {
-    module_invoke_all('exit');
-    module_implements_write_cache();
+    $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 instanceof CachedModuleHandlerInterface) {
+      $this->moduleHandler->writeCache();
+    }
     system_run_automated_cron();
   }
 
diff --git a/core/lib/Drupal/Core/Extension/CachedModuleHandler.php b/core/lib/Drupal/Core/Extension/CachedModuleHandler.php
new file mode 100644
index 000000000000..a3f3eac4eeb2
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/CachedModuleHandler.php
@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Extension\CachedModuleHandler.
+ */
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+
+/**
+ * Class that manages enabled modules in a Drupal installation.
+ */
+class CachedModuleHandler extends ModuleHandler implements CachedModuleHandlerInterface {
+
+  /**
+   * State key/value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $state;
+
+  /**
+   * Cache backend for storing enabled modules.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $bootstrapCache;
+
+  /**
+   * Whether the cache needs to be written.
+   *
+   * @var boolean
+   */
+  protected $cacheNeedsWriting = FALSE;
+
+  /**
+   * Constructs a new CachedModuleHandler object.
+   */
+  public function __construct(array $module_list = array(), KeyValueStoreInterface $state, CacheBackendInterface $bootstrap_cache) {
+    parent::__construct($module_list);
+    $this->state = $state;
+    $this->bootstrapCache = $bootstrap_cache;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\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\Extension\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\Extension\CachedModuleHandlerInterface::writeCache().
+   */
+  public function writeCache() {
+    if ($this->cacheNeedsWriting) {
+      $this->bootstrapCache->set('module_implements', $this->implementations);
+      $this->cacheNeedsWriting = FALSE;
+    }
+  }
+
+  /**
+   * Overrides \Drupal\Core\Extension\ModuleHandler::getImplementationInfo().
+   */
+  protected function getImplementationInfo($hook) {
+    if (!isset($this->implementations)) {
+      $this->implementations = $this->getCachedImplementationInfo();
+    }
+    if (!isset($this->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;
+      $this->implementations[$hook] = parent::getImplementationInfo($hook);
+    }
+    else {
+      foreach ($this->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($this->implementations[$hook][$module]);
+          $this->cacheNeedsWriting = TRUE;
+        }
+      }
+    }
+    return $this->implementations[$hook];
+  }
+
+  /**
+   * Overrides \Drupal\Core\Extension\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();
+    }
+    if (!isset($this->hookInfo)) {
+      if ($cache = $this->bootstrapCache->get('hook_info')) {
+        $this->hookInfo = $cache->data;
+      }
+      else {
+        $this->hookInfo = parent::getHookInfo();
+        $this->bootstrapCache->set('hook_info', $this->hookInfo);
+      }
+    }
+    return $this->hookInfo;
+  }
+
+  /**
+   * Retrieves hook implementation info from the cache.
+   */
+  protected function getCachedImplementationInfo() {
+    if ($cache = $this->bootstrapCache->get('module_implements')) {
+      return $cache->data;
+    }
+    else {
+      return array();
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Extension/CachedModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/CachedModuleHandlerInterface.php
new file mode 100644
index 000000000000..cca18dc905ae
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/CachedModuleHandlerInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Extension\CachedModuleHandlerInterface.
+ */
+
+namespace Drupal\Core\Extension;
+
+/**
+ * Interface for cacheable module handlers.
+ */
+interface CachedModuleHandlerInterface extends ModuleHandlerInterface {
+
+  /**
+   * Write the hook implementation info to the cache.
+   */
+  public function writeCache();
+
+}
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
new file mode 100644
index 000000000000..8e1cdb683338
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -0,0 +1,522 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Extension\ModuleHandler.
+ */
+
+namespace Drupal\Core\Extension;
+
+use Drupal\Component\Graph\Graph;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+
+/**
+ * Class that manages enabled modules in a Drupal installation.
+ */
+class ModuleHandler implements ModuleHandlerInterface {
+
+  /**
+   * List of loaded files.
+   *
+   * @var array
+   *   An associative array whose keys are file paths of loaded files, relative
+   *   to the application's root directory.
+   */
+  protected $loadedFiles;
+
+  /**
+   * List of enabled bootstrap modules.
+   *
+   * @var array
+   */
+  protected $bootstrapModules;
+
+  /**
+   * List of enabled modules.
+   *
+   * @var array
+   *   An associative array whose keys are the names of the modules and whose
+   *   values are the module filenames.
+   */
+  protected $moduleList;
+
+  /**
+   * Boolean indicating whether modules have been loaded.
+   *
+   * @var bool
+   */
+  protected $loaded = FALSE;
+
+  /**
+   * List of hook implementations keyed by hook name.
+   *
+   * @var array
+   */
+  protected $implementations;
+
+  /**
+   * Information returned by hook_hook_info() implementations.
+   *
+   * @var array
+   */
+  protected $hookInfo;
+
+  /**
+   * List of alter hook implementations keyed by hook name(s).
+   *
+   * @var array
+   */
+  protected $alterFunctions;
+
+  /**
+   * Constructs a ModuleHandler object.
+   *
+   * @param array $module_list
+   *   An associative array whose keys are the names of enabled modules and
+   *   whose values are the module filenames. This is normally the
+   *   %container.modules% parameter being set up by DrupalKernel.
+   *
+   * @see \Drupal\Core\DrupalKernel
+   * @see \Drupal\Core\CoreBundle
+   */
+  public function __construct(array $module_list = array()) {
+    $this->moduleList = $module_list;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::load().
+   */
+  public function load($name) {
+    if (isset($this->loadedFiles[$name])) {
+      return TRUE;
+    }
+
+    if (isset($this->moduleList[$name])) {
+      $filename = $this->moduleList[$name];
+      include_once DRUPAL_ROOT . '/' . $filename;
+      $this->loadedFiles[$name] = TRUE;
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::loadAll().
+   */
+  public function loadAll() {
+    if (!$this->loaded) {
+      foreach ($this->moduleList as $module => $filename) {
+        $this->load($module);
+      }
+      $this->loaded = TRUE;
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::reload().
+   */
+  public function reload() {
+    $this->loaded = FALSE;
+    $this->loadAll();
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::loadBootstrapModules().
+   */
+  public function loadBootstrapModules() {
+    if (!$this->loaded) {
+      foreach ($this->getBootstrapModules() as $module) {
+        $this->load($module);
+      }
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::isLoaded().
+   */
+  public function isLoaded() {
+    return $this->loaded;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::getModuleList().
+   */
+  public function getModuleList() {
+    return $this->moduleList;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::setModuleList().
+   */
+  public function setModuleList(array $module_list = array()) {
+    $this->moduleList = $module_list;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::getBootstrapModules().
+   */
+  public function getBootstrapModules() {
+    // The basic module handler does not know anything about how to retrieve a
+    // list of bootstrap modules.
+    return array();
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::buildModuleDependencies().
+   */
+  public function buildModuleDependencies(array $modules) {
+    foreach ($modules as $name => $module) {
+      $graph[$module->name]['edges'] = array();
+      if (isset($module->info['dependencies']) && is_array($module->info['dependencies'])) {
+        foreach ($module->info['dependencies'] as $dependency) {
+          $dependency_data = $this->parseDependency($dependency);
+          $graph[$module->name]['edges'][$dependency_data['name']] = $dependency_data;
+        }
+      }
+    }
+    $graph_object = new Graph($graph);
+    $graph = $graph_object->searchAndSort();
+    foreach ($graph as $module_name => $data) {
+      $modules[$module_name]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : array();
+      $modules[$module_name]->requires = isset($data['paths']) ? $data['paths'] : array();
+      $modules[$module_name]->sort = $data['weight'];
+    }
+    return $modules;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::moduleExists().
+   */
+  public function moduleExists($module) {
+    return isset($this->moduleList[$module]);
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::loadAllIncludes().
+   */
+  public function loadAllIncludes($type, $name = NULL) {
+    foreach ($this->moduleList as $module => $filename) {
+      $this->loadInclude($module, $type, $name);
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::loadInclude().
+   */
+  public function loadInclude($module, $type, $name = NULL) {
+    if ($type == 'install') {
+      // Make sure the installation API is available
+      include_once DRUPAL_ROOT . '/core/includes/install.inc';
+    }
+
+    $name = $name ?: $module;
+    $file = DRUPAL_ROOT . '/' . dirname($this->moduleList[$module]) . "/$name.$type";
+    if (is_file($file)) {
+      require_once $file;
+      return $file;
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::getImplementations().
+   */
+  public function getImplementations($hook) {
+    $implementations = $this->getImplementationInfo($hook);
+    return array_keys($implementations);
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::resetImplementations().
+   */
+  public function resetImplementations() {
+    $this->implementations = NULL;
+    $this->hookInfo = NULL;
+    $this->alterFunctions = NULL;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::implementsHook().
+   */
+  public function implementsHook($module, $hook) {
+    $function = $module . '_' . $hook;
+    if (function_exists($function)) {
+      return TRUE;
+    }
+    // If the hook implementation does not exist, check whether it lives in an
+    // optional include file registered via hook_hook_info().
+    $hook_info = $this->getHookInfo();
+    if (isset($hook_info[$hook]['group'])) {
+      $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']);
+      if (function_exists($function)) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::invokeAll().
+   */
+  public function invokeAll($hook, $args = array()) {
+    $return = array();
+    $implementations = $this->getImplementations($hook);
+    foreach ($implementations as $module) {
+      $function = $module . '_' . $hook;
+      if (function_exists($function)) {
+        $result = call_user_func_array($function, $args);
+        if (isset($result) && is_array($result)) {
+          $return = NestedArray::mergeDeep($return, $result);
+        }
+        elseif (isset($result)) {
+          $return[] = $result;
+        }
+      }
+    }
+
+    return $return;
+  }
+
+  /**
+   * Implements \Drupal\Core\Extension\ModuleHandlerInterface::alter().
+   */
+  public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
+    // Most of the time, $type is passed as a string, so for performance,
+    // normalize it to that. When passed as an array, usually the first item in
+    // the array is a generic type, and additional items in the array are more
+    // specific variants of it, as in the case of array('form', 'form_FORM_ID').
+    if (is_array($type)) {
+      $cid = implode(',', $type);
+      $extra_types = $type;
+      $type = array_shift($extra_types);
+      // Allow if statements in this function to use the faster isset() rather
+      // than !empty() both when $type is passed as a string, or as an array with
+      // one item.
+      if (empty($extra_types)) {
+        unset($extra_types);
+      }
+    }
+    else {
+      $cid = $type;
+    }
+
+    // Some alter hooks are invoked many times per page request, so store the
+    // list of functions to call, and on subsequent calls, iterate through them
+    // quickly.
+    if (!isset($this->alterFunctions[$cid])) {
+      $this->alterFunctions[$cid] = array();
+      $hook = $type . '_alter';
+      $modules = $this->getImplementations($hook);
+      if (!isset($extra_types)) {
+        // For the more common case of a single hook, we do not need to call
+        // function_exists(), since $this->getImplementations() returns only modules with
+        // implementations.
+        foreach ($modules as $module) {
+          $this->alterFunctions[$cid][] = $module . '_' . $hook;
+        }
+      }
+      else {
+        // For multiple hooks, we need $modules to contain every module that
+        // implements at least one of them.
+        $extra_modules = array();
+        foreach ($extra_types as $extra_type) {
+          $extra_modules = array_merge($extra_modules, $this->getImplementations($extra_type . '_alter'));
+        }
+        // If any modules implement one of the extra hooks that do not implement
+        // the primary hook, we need to add them to the $modules array in their
+        // appropriate order. $this->getImplementations() can only return ordered
+        // implementations of a single hook. To get the ordered implementations
+        // of multiple hooks, we mimic the $this->getImplementations() logic of first
+        // ordering by $this->getModuleList(), and then calling
+        // $this->alter('module_implements').
+        if (array_diff($extra_modules, $modules)) {
+          // Merge the arrays and order by getModuleList().
+          $modules = array_intersect(array_keys($this->moduleList), array_merge($modules, $extra_modules));
+          // Since $this->getImplementations() already took care of loading the necessary
+          // include files, we can safely pass FALSE for the array values.
+          $implementations = array_fill_keys($modules, FALSE);
+          // Let modules adjust the order solely based on the primary hook. This
+          // ensures the same module order regardless of whether this if block
+          // runs. Calling $this->alter() recursively in this way does not result
+          // in an infinite loop, because this call is for a single $type, so we
+          // won't end up in this code block again.
+          $this->alter('module_implements', $implementations, $hook);
+          $modules = array_keys($implementations);
+        }
+        foreach ($modules as $module) {
+          // Since $modules is a merged array, for any given module, we do not
+          // know whether it has any particular implementation, so we need a
+          // function_exists().
+          $function = $module . '_' . $hook;
+          if (function_exists($function)) {
+            $this->alterFunctions[$cid][] = $function;
+          }
+          foreach ($extra_types as $extra_type) {
+            $function = $module . '_' . $extra_type . '_alter';
+            if (function_exists($function)) {
+              $this->alterFunctions[$cid][] = $function;
+            }
+          }
+        }
+      }
+      // Allow the theme to alter variables after the theme system has been
+      // initialized.
+      global $theme, $base_theme_info;
+      if (isset($theme)) {
+        $theme_keys = array();
+        foreach ($base_theme_info as $base) {
+          $theme_keys[] = $base->name;
+        }
+        $theme_keys[] = $theme;
+        foreach ($theme_keys as $theme_key) {
+          $function = $theme_key . '_' . $hook;
+          if (function_exists($function)) {
+            $this->alterFunctions[$cid][] = $function;
+          }
+          if (isset($extra_types)) {
+            foreach ($extra_types as $extra_type) {
+              $function = $theme_key . '_' . $extra_type . '_alter';
+              if (function_exists($function)) {
+                $this->alterFunctions[$cid][] = $function;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    foreach ($this->alterFunctions[$cid] as $function) {
+      $function($data, $context1, $context2);
+    }
+  }
+
+  /**
+   * Provides information about modules' implementations of a hook.
+   *
+   * @param string $hook
+   *   The name of the hook (e.g. "help" or "menu").
+   *
+   * @return array
+   *   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) {
+    if (isset($this->implementations[$hook])) {
+      return $this->implementations[$hook];
+    }
+    $this->implementations[$hook] = array();
+    $hook_info = $this->getHookInfo();
+    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
+   *   An associative array whose keys are hook names and whose values are an
+   *   associative array containing a group name. The structure of the array
+   *   is the same as the return value of hook_hook_info().
+   *
+   * @see hook_hook_info()
+   */
+  protected function getHookInfo() {
+    if ($this->hookInfo) {
+      return $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);
+        }
+      }
+    }
+    // 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;
+  }
+
+  /**
+   * Parses a dependency for comparison by drupal_check_incompatibility().
+   *
+   * @param $dependency
+   *   A dependency string, for example 'foo (>=8.x-4.5-beta5, 3.x)'.
+   *
+   * @return
+   *   An associative array with three keys:
+   *   - 'name' includes the name of the thing to depend on (e.g. 'foo').
+   *   - 'original_version' contains the original version string (which can be
+   *     used in the UI for reporting incompatibilities).
+   *   - 'versions' is a list of associative arrays, each containing the keys
+   *     'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<',
+   *     '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'.
+   *   Callers should pass this structure to drupal_check_incompatibility().
+   *
+   * @see drupal_check_incompatibility()
+   */
+  protected function parseDependency($dependency) {
+    // We use named subpatterns and support every op that version_compare
+    // supports. Also, op is optional and defaults to equals.
+    $p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
+    // Core version is always optional: 8.x-2.x and 2.x is treated the same.
+    $p_core = '(?:' . preg_quote(DRUPAL_CORE_COMPATIBILITY) . '-)?';
+    $p_major = '(?P<major>\d+)';
+    // By setting the minor version to x, branches can be matched.
+    $p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
+    $value = array();
+    $parts = explode('(', $dependency, 2);
+    $value['name'] = trim($parts[0]);
+    if (isset($parts[1])) {
+      $value['original_version'] = ' (' . $parts[1];
+      foreach (explode(',', $parts[1]) as $version) {
+        if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) {
+          $op = !empty($matches['operation']) ? $matches['operation'] : '=';
+          if ($matches['minor'] == 'x') {
+            // Drupal considers "2.x" to mean any version that begins with
+            // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
+            // on the other hand, treats "x" as a string; so to
+            // version_compare(), "2.x" is considered less than 2.0. This
+            // means that >=2.x and <2.x are handled by version_compare()
+            // as we need, but > and <= are not.
+            if ($op == '>' || $op == '<=') {
+              $matches['major']++;
+            }
+            // Equivalence can be checked by adding two restrictions.
+            if ($op == '=' || $op == '==') {
+              $value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x');
+              $op = '>=';
+            }
+          }
+          $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']);
+        }
+      }
+    }
+    return $value;
+  }
+}
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
new file mode 100644
index 000000000000..7cb84186aafe
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
@@ -0,0 +1,238 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Extension\ModuleHandlerInterface.
+ */
+
+namespace Drupal\Core\Extension;
+
+/**
+ * Interface for classes that manage a set of enabled modules.
+ *
+ * Classes implementing this interface work with a fixed list of modules and are
+ * responsible for loading module files and maintaining information about module
+ * dependencies and hook implementations.
+ */
+interface ModuleHandlerInterface {
+
+  /**
+   * Includes a module's .module file.
+   *
+   * This prevents including a module more than once.
+   *
+   * @param string $name
+   *   The name of the module to load.
+   *
+   * @return bool
+   *   TRUE if the item is loaded or has already been loaded.
+   */
+  public function load($name);
+
+  /**
+   * Loads all enabled modules.
+   */
+  public function loadAll();
+
+  /**
+   * Loads all enabled bootstrap modules.
+   */
+  public function loadBootstrapModules();
+
+  /**
+   * Returns whether all modules have been loaded.
+   *
+   * @return bool
+   *   A Boolean indicating whether all modules have been loaded. This means all
+   *   modules; the load status of bootstrap modules cannot be checked.
+   */
+  public function isLoaded();
+
+  /**
+   * Reloads all enabled modules.
+   */
+  public function reload();
+
+  /**
+   * Returns a list of currently active modules.
+   *
+   * @return array
+   *   An associative array whose keys are the names of the modules and whose
+   *   values are the module filenames.
+   */
+  public function getModuleList();
+
+  /**
+   * Explicitly sets the moduleList property to the passed in array of modules.
+   *
+   * @param array $module_list
+   *   An associative array whose keys are the names of the modules and whose
+   *   values are the module filenames.
+   */
+  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 array $modules
+   *   An array of module objects keyed by module name. Each object contains
+   *   information discovered during a Drupal\Core\SystemListing scan.
+   *
+   * @return
+   *   The same array with the new keys for each module:
+   *   - requires: An array with the keys being the modules that this module
+   *     requires.
+   *   - required_by: An array with the keys being the modules that will not work
+   *     without this module.
+   *
+   * @see \Drupal\Core\SystemListing
+   */
+  public function buildModuleDependencies(array $modules);
+
+  /**
+   * Determines whether a given module is enabled.
+   *
+   * @param string $module
+   *   The name of the module (without the .module extension).
+   *
+   * @return bool
+   *   TRUE if the module is both installed and enabled.
+   */
+  public function moduleExists($module);
+
+  /**
+   * Loads an include file for each enabled module.
+   *
+   * @param string $type
+   *   The include file's type (file extension).
+   * @param string $name
+   *   (optional) The base file name (without the $type extension). If omitted,
+   *   each module's name is used; i.e., "$module.$type" by default.
+   */
+  public function loadAllIncludes($type, $name = NULL);
+
+  /**
+   * Loads a module include file.
+   *
+   * Examples:
+   * @code
+   *   // Load node.admin.inc from the node module.
+   *   $this->loadInclude('node', 'inc', 'node.admin');
+   *   // Load content_types.inc from the node module.
+   *   $this->loadInclude('node', 'inc', ''content_types');
+   * @endcode
+   *
+   * @param string $module
+   *   The module to which the include file belongs.
+   * @param string $type
+   *   The include file's type (file extension).
+   * @param string $name
+   *   (optional) The base file name (without the $type extension). If omitted,
+   *   $module is used; i.e., resulting in "$module.$type" by default.
+   *
+   * @return string|false
+   *   The name of the included file, if successful; FALSE otherwise.
+   */
+  public function loadInclude($module, $type, $name = NULL);
+
+  /**
+   * Determines which modules are implementing a hook.
+   *
+   * @param string $hook
+   *   The name of the hook (e.g. "help" or "menu").
+   *
+   * @return array
+   *   An array with the names of the modules which are implementing this hook.
+   */
+  public function getImplementations($hook);
+
+  /**
+   * Resets the cached list of hook implementations.
+   */
+  public function resetImplementations();
+
+  /**
+   * Returns whether a given module implements a given hook.
+   *
+   * @param string $module
+   *   The name of the module (without the .module extension).
+   * @param string $hook
+   *   The name of the hook (e.g. "help" or "menu").
+   *
+   * @return bool
+   *   TRUE if the module is both installed and enabled, and the hook is
+   *   implemented in that module.
+   */
+  public function implementsHook($module, $hook);
+
+  /**
+   * Invokes a hook in all enabled modules that implement it.
+   *
+   * @param string $hook
+   *   The name of the hook to invoke.
+   * @param ...
+   *   Arguments to pass to the hook.
+   *
+   * @return array
+   *   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 = array());
+
+  /**
+   * Passes alterable variables to specific hook_TYPE_alter() implementations.
+   *
+   * This dispatch function hands off the passed-in variables to type-specific
+   * hook_TYPE_alter() implementations in modules. It ensures a consistent
+   * interface for all altering operations.
+   *
+   * A maximum of 2 alterable arguments is supported. In case more arguments need
+   * to be passed and alterable, modules provide additional variables assigned by
+   * reference in the last $context argument:
+   * @code
+   *   $context = array(
+   *     'alterable' => &$alterable,
+   *     'unalterable' => $unalterable,
+   *     'foo' => 'bar',
+   *   );
+   *   $this->alter('mymodule_data', $alterable1, $alterable2, $context);
+   * @endcode
+   *
+   * Note that objects are always passed by reference in PHP5. If it is absolutely
+   * required that no implementation alters a passed object in $context, then an
+   * object needs to be cloned:
+   * @code
+   *   $context = array(
+   *     'unalterable_object' => clone $object,
+   *   );
+   *   $this->alter('mymodule_data', $data, $context);
+   * @endcode
+   *
+   * @param string|array $type
+   *   A string describing the type of the alterable $data. 'form', 'links',
+   *   'node_content', and so on are several examples. Alternatively can be an
+   *   array, in which case hook_TYPE_alter() is invoked for each value in the
+   *   array, ordered first by module, and then for each module, in the order of
+   *   values in $type. For example, when Form API is using $this->alter() to
+   *   execute both hook_form_alter() and hook_form_FORM_ID_alter()
+   *   implementations, it passes array('form', 'form_' . $form_id) for $type.
+   * @param mixed $data
+   *   The variable that will be passed to hook_TYPE_alter() implementations to be
+   *   altered. The type of this variable depends on the value of the $type
+   *   argument. For example, when altering a 'form', $data will be a structured
+   *   array. When altering a 'profile', $data will be an object.
+   * @param mixed $context1
+   *   (optional) An additional variable that is passed by reference.
+   * @param mixed $context2
+   *   (optional) An additional variable that is passed by reference. If more
+   *   context needs to be provided to implementations, then this should be an
+   *   associative array as described above.
+   */
+  public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL);
+
+}
diff --git a/core/lib/Drupal/Core/Routing/RouteBuilder.php b/core/lib/Drupal/Core/Routing/RouteBuilder.php
index 6f7a86edaf2a..232e47e45c6b 100644
--- a/core/lib/Drupal/Core/Routing/RouteBuilder.php
+++ b/core/lib/Drupal/Core/Routing/RouteBuilder.php
@@ -13,6 +13,7 @@
 use Symfony\Component\Routing\RouteCollection;
 use Symfony\Component\Routing\Route;
 
+use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Lock\LockBackendInterface;
 
 /**
@@ -44,6 +45,13 @@ class RouteBuilder {
    */
   protected $dispatcher;
 
+  /**
+   * The extension handler for retieving the list of enabled modules.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
   /**
    * Construcs the RouteBuilder using the passed MatcherDumperInterface.
    *
@@ -54,10 +62,11 @@ class RouteBuilder {
    * @param \Symfony\Component\EventDispatcherEventDispatcherInterface
    *   The event dispatcher to notify of routes.
    */
-  public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher) {
+  public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher, ModuleHandlerInterface $module_handler) {
     $this->dumper = $dumper;
     $this->lock = $lock;
     $this->dispatcher = $dispatcher;
+    $this->moduleHandler = $module_handler;
   }
 
   /**
@@ -76,11 +85,9 @@ public function rebuild() {
 
     // We need to manually call each module so that we can know which module
     // a given item came from.
-    // @todo Use an injected Extension service rather than module_list():
-    //   http://drupal.org/node/1331486.
-    foreach (module_list() as $module) {
+    foreach ($this->moduleHandler->getModuleList() as $module => $filename) {
       $collection = new RouteCollection();
-      $routing_file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . '/' . $module . '.routing.yml';
+      $routing_file = DRUPAL_ROOT . '/' . dirname($filename) . '/' . $module . '.routing.yml';
       if (file_exists($routing_file)) {
         $routes = $parser->parse(file_get_contents($routing_file));
         if (!empty($routes)) {
diff --git a/core/lib/Drupal/Core/Utility/ThemeRegistry.php b/core/lib/Drupal/Core/Utility/ThemeRegistry.php
index bc9691caf655..eb2dd80566bb 100644
--- a/core/lib/Drupal/Core/Utility/ThemeRegistry.php
+++ b/core/lib/Drupal/Core/Utility/ThemeRegistry.php
@@ -42,12 +42,14 @@ class ThemeRegistry extends CacheArray {
    *   The bin to cache the array.
    * @param array $tags
    *   (optional) The tags to specify for the cache item.
+   * @param bool $modules_loaded
+   *   Whether all modules have already been loaded.
    */
-  function __construct($cid, $bin, $tags) {
+  function __construct($cid, $bin, $tags, $modules_loaded = FALSE) {
     $this->cid = $cid;
     $this->bin = $bin;
     $this->tags = $tags;
-    $this->persistable = module_load_all(NULL) && $_SERVER['REQUEST_METHOD'] == 'GET';
+    $this->persistable = $modules_loaded && $_SERVER['REQUEST_METHOD'] == 'GET';
 
     $data = array();
     if ($this->persistable && $cached = cache($this->bin)->get($this->cid)) {
diff --git a/core/modules/breakpoint/breakpoint.install b/core/modules/breakpoint/breakpoint.install
index 0e6bc30410d6..cba7b1b5af15 100644
--- a/core/modules/breakpoint/breakpoint.install
+++ b/core/modules/breakpoint/breakpoint.install
@@ -18,6 +18,5 @@ function breakpoint_enable() {
   _breakpoint_theme_enabled(array_keys($themes));
 
   // Import breakpoints from modules.
-  $modules = module_list();
-  _breakpoint_modules_enabled(array_keys($modules));
+  _breakpoint_modules_enabled(array_keys(drupal_container()->get('module_handler')->getModuleList()));
 }
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php
index ab03d41171ee..f3f6352e5d58 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php
@@ -72,7 +72,7 @@ function testCommentEnable() {
     $edit = array();
     $edit['modules[Core][comment][enable]'] = FALSE;
     $this->drupalPost('admin/modules', $edit, t('Save configuration'));
-    $this->resetAll();
+    $this->rebuildContainer();
     $this->assertFalse(module_exists('comment'), 'Comment module disabled.');
 
     // Enable core content type modules (book, and poll).
@@ -85,7 +85,7 @@ function testCommentEnable() {
     $edit = array();
     $edit['modules[Core][comment][enable]'] = 'comment';
     $this->drupalPost('admin/modules', $edit, t('Save configuration'));
-    $this->resetAll();
+    $this->rebuildContainer();
     $this->assertTrue(module_exists('comment'), 'Comment module enabled.');
 
     // Create nodes of each type.
diff --git a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php
index 8b0ce69290da..6d213f632e2a 100644
--- a/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php
+++ b/core/modules/entity/lib/Drupal/entity/Tests/EntityDisplayTest.php
@@ -14,7 +14,7 @@
  */
 class EntityDisplayTest extends DrupalUnitTestBase {
 
-  public static $modules = array('entity_test');
+  public static $modules = array('entity', 'field', 'entity_test');
 
   public static function getInfo() {
     return array(
@@ -27,7 +27,7 @@ public static function getInfo() {
   protected function setUp() {
     parent::setUp();
 
-    $this->enableModules(array('system', 'entity', 'field'));
+    $this->enableModules(array('field'));
   }
 
   /**
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index e30f16a3fbe3..e350a2e367c8 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -541,7 +541,7 @@ function field_modules_disabled($modules) {
 function field_sync_field_status() {
   // Refresh the 'active' and 'storage_active' columns according to the current
   // set of enabled modules.
-  $modules = module_list();
+  $modules = array_keys(drupal_container()->get('module_handler')->getModuleList());
   foreach ($modules as $module_name) {
     field_associate_fields($module_name);
   }
diff --git a/core/modules/filter/filter.install b/core/modules/filter/filter.install
index 39ef59bf7cd6..536d4f6fedd8 100644
--- a/core/modules/filter/filter.install
+++ b/core/modules/filter/filter.install
@@ -47,9 +47,6 @@ function filter_update_8001() {
     // Generate a UUID.
     $format['uuid'] = $uuid->generate();
 
-    // Add user roles.
-    $format['roles'] = array_keys(user_roles(FALSE, 'use text format ' . $format['format']));
-
     // Retrieve and prepare all filters.
     $filters = db_query('SELECT name, module, status, weight, settings FROM {filter} WHERE format = :format ORDER BY weight, module, name', array(
       ':format' => $id,
diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
index 11853b582e5b..d4aba03a77c9 100644
--- a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
+++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php
@@ -108,7 +108,7 @@ function testEnableForumField() {
     $edit['modules[Core][forum][enable]'] = FALSE;
     $this->drupalPost('admin/modules', $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
-    system_list_reset();
+    $this->rebuildContainer();
     $this->assertFalse(module_exists('forum'), 'Forum module is not enabled.');
 
     // Attempt to re-enable the Forum module and ensure it does not try to
@@ -117,7 +117,7 @@ function testEnableForumField() {
     $edit['modules[Core][forum][enable]'] = 'forum';
     $this->drupalPost('admin/modules', $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
-    system_list_reset();
+    $this->rebuildContainer();
     $this->assertTrue(module_exists('forum'), 'Forum module is enabled.');
   }
 
diff --git a/core/modules/jsonld/lib/Drupal/jsonld/Tests/RdfSchemaSerializationTest.php b/core/modules/jsonld/lib/Drupal/jsonld/Tests/RdfSchemaSerializationTest.php
index c4422aaa891b..eacf9dffa79b 100644
--- a/core/modules/jsonld/lib/Drupal/jsonld/Tests/RdfSchemaSerializationTest.php
+++ b/core/modules/jsonld/lib/Drupal/jsonld/Tests/RdfSchemaSerializationTest.php
@@ -15,6 +15,8 @@
 
 class RdfSchemaSerializationTest extends DrupalUnitTestBase {
 
+  public static $modules = array('system');
+
   public static function getInfo() {
     return array(
       'name' => 'Site schema JSON-LD serialization',
@@ -27,9 +29,8 @@ public static function getInfo() {
    * Tests the serialization of site schemas.
    */
   function testSchemaSerialization() {
-    // In order to use url() the url_alias table must be installed, so system
-    // is enabled.
-    $this->enableModules(array('system'));
+    // url() requires the {url_alias} table.
+    $this->installSchema('system', 'url_alias');
 
     $entity_type = $bundle = 'entity_test';
 
diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php
index 436b371b5b3f..8aea763917a0 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php
@@ -139,7 +139,7 @@ protected function languageNegotiationUpdate($op = 'enable') {
       $function = "module_{$op}";
       $function($modules);
       // Reset hook implementation cache.
-      module_implements_reset();
+      $this->container->get('module_handler')->resetImplementations();
     }
 
     drupal_static_reset('language_types_info');
diff --git a/core/modules/layout/lib/Drupal/layout/Plugin/Derivative/Layout.php b/core/modules/layout/lib/Drupal/layout/Plugin/Derivative/Layout.php
index a6c4ea947dfe..c5d0ddeaa1df 100644
--- a/core/modules/layout/lib/Drupal/layout/Plugin/Derivative/Layout.php
+++ b/core/modules/layout/lib/Drupal/layout/Plugin/Derivative/Layout.php
@@ -58,11 +58,12 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
     $available_layout_providers = array();
 
     // Add all modules as possible layout providers.
-    foreach (module_list() as $module) {
+    // @todo Inject the module handler.
+    foreach (drupal_container()->get('module_handler')->getModuleList() as $module => $filename) {
       $available_layout_providers[$module] = array(
         'type' => 'module',
         'provider' => $module,
-        'dir' => drupal_get_path('module', $module),
+        'dir' => dirname($filename),
       );
     }
 
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
index bd1f5a3022a9..b9348517d3f3 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
@@ -315,6 +315,7 @@ function testUpdateProjects() {
     // modules not hidden. locale_test_system_info_alter() modifies the project
     // info of the locale_test and locale_test_translate modules.
     state()->set('locale.test_system_info_alter', TRUE);
+    $this->resetAll();
 
     // Check if interface translation data is collected from hook_info.
     $projects = locale_translation_project_list();
@@ -332,6 +333,7 @@ function testUpdateProjectsHidden() {
 
     // Make the test modules look like a normal custom module.
     state()->set('locale.test_system_info_alter', TRUE);
+    $this->resetAll();
 
     // Set test condition: include disabled modules when building a project list.
     $edit = array(
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
index b09df752e80e..b17e37100a36 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -50,18 +50,6 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
    */
   public static $modules = array();
 
-  /**
-   * Fixed module list being used by this test.
-   *
-   * @var array
-   *   An associative array containing the required data for the $fixed_list
-   *   argument of module_list().
-   *
-   * @see UnitTestBase::setUp()
-   * @see UnitTestBase::enableModules()
-   */
-  private $moduleList = array();
-
   private $moduleFiles;
   private $themeFiles;
   private $themeData;
@@ -104,8 +92,6 @@ protected function setUp() {
     $this->kernel = new DrupalKernel('testing', TRUE, drupal_classloader(), FALSE);
     $this->kernel->boot();
 
-    // Ensure that the module list is initially empty.
-    $this->moduleList = array();
     // Collect and set a fixed module list.
     $class = get_class($this);
     $modules = array();
@@ -132,11 +118,15 @@ public function containerBuild($container) {
     global $conf;
     // Keep the container object around for tests.
     $this->container = $container;
+
     $container->register('lock', 'Drupal\Core\Lock\NullLockBackend');
+
     $conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend');
+
     $container
       ->register('config.storage', 'Drupal\Core\Config\FileStorage')
       ->addArgument($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
+
     $conf['keyvalue_default'] = 'keyvalue.memory';
     $container->set('keyvalue.memory', $this->keyValueFactory);
     if (!$container->has('keyvalue')) {
@@ -172,7 +162,7 @@ protected function installSchema($module, $table) {
     // file depends on many other factors. To prevent differences in test
     // behavior and non-reproducible test failures, we only allow the schema of
     // explicitly loaded/enabled modules to be installed.
-    if (!module_exists($module)) {
+    if (!$this->container->get('module_handler')->moduleExists($module)) {
       throw new \RuntimeException(format_string("'@module' module is not enabled.", array(
         '@module' => $module,
       )));
@@ -207,27 +197,30 @@ protected function installSchema($module, $table) {
    *   Defaults to TRUE. If FALSE, the new modules are only added to the fixed
    *   module list and loaded.
    *
-   * @todo Remove this method as soon as there is an Extensions service
-   *   implementation that is able to manage a fixed module list.
+   * @todo Remove $install argument and replace all callers that do not pass
+   *   FALSE with module_enable().
    */
   protected function enableModules(array $modules, $install = TRUE) {
-    // Set the modules in the fixed module_list().
-    $new_enabled = array();
-    foreach ($modules as $module) {
-      $this->moduleList[$module]['filename'] = drupal_get_filename('module', $module);
-      $new_enabled[$module] = dirname($this->moduleList[$module]['filename']);
-      module_list(NULL, $this->moduleList);
-
-      // Call module_enable() to enable (install) the new module.
-      if ($install) {
-        module_enable(array($module), FALSE);
-      }
+    if ($install) {
+      module_enable($modules, FALSE);
     }
-    // Otherwise, only ensure that the new modules are loaded.
-    if (!$install) {
-      module_load_all(FALSE, TRUE);
-      module_implements_reset();
+    // Explicitly set the list of modules in the extension handler.
+    else {
+      $module_handler = $this->container->get('module_handler');
+      $module_filenames = $module_handler->getModuleList();
+      foreach ($modules as $module) {
+        $module_filenames[$module] = drupal_get_filename('module', $module);
+      }
+      $module_handler->setModuleList($module_filenames);
+      $module_handler->resetImplementations();
+      $this->kernel->updateModules($module_filenames, $module_filenames);
     }
+    // Regardless of loaded or installed, ensure isLoaded() is TRUE in order to
+    // make theme() work.
+    // Note that the kernel has rebuilt the container; this $module_handler is
+    // no longer the $module_handler instance from above.
+    $module_handler = $this->container->get('module_handler');
+    $module_handler->reload();
   }
 
 }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
index e8068fade6dd..79e6d9c60d15 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/TestBase.php
@@ -921,26 +921,24 @@ protected function prepareConfigDirectories() {
   /**
    * Rebuild drupal_container().
    *
+   * Use this to build a new kernel and service container. For example, when the
+   * list of enabled modules is changed via the internal browser, in which case
+   * the test process still contains an old kernel and service container with an
+   * old module list.
+   *
    * @todo Fix http://drupal.org/node/1708692 so that module enable/disable
    *   changes are immediately reflected in drupal_container(). Until then,
    *   tests can invoke this workaround when requiring services from newly
    *   enabled modules to be immediately available in the same request.
+   *
+   * @see TestBase::prepareEnvironment()
+   * @see TestBase::tearDown()
    */
   protected function rebuildContainer() {
-    // Create a new DrupalKernel for testing purposes, now that all required
-    // modules have been enabled. This also stores a new dependency injection
-    // container in drupal_container(). Drupal\simpletest\TestBase::tearDown()
-    // restores the original container.
-    // @see Drupal\Core\DrupalKernel::initializeContainer()
     $this->kernel = new DrupalKernel('testing', FALSE, drupal_classloader(), FALSE);
-    // 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
-    // as it is not used to handle a request.
     $this->kernel->boot();
-    // The DrupalKernel does not update the container in drupal_container(), but
-    // replaces it with a new object. We therefore need to replace the minimal
-    // boostrap container that has been set up by TestBase::prepareEnvironment().
+    // DrupalKernel replaces the container in drupal_container() with a
+    // different object, so we need to replace the instance on this test class.
     $this->container = drupal_container();
   }
 
@@ -1033,10 +1031,6 @@ protected function tearDown() {
       drupal_valid_test_ua($this->originalPrefix);
     }
 
-    // Reset module list and module load status.
-    module_list_reset();
-    module_load_all(FALSE, TRUE);
-
     // Restore original shutdown callbacks.
     $callbacks = &drupal_register_shutdown_function();
     $callbacks = $this->originalShutdownCallbacks;
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php
index 6bca9d6aca49..242338efc533 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/Tests/DrupalUnitTestBaseTest.php
@@ -39,7 +39,7 @@ function testSetUp() {
     // Verify that specified $modules have been loaded.
     $this->assertTrue(function_exists('entity_test_permission'), "$module.module was loaded.");
     // Verify that there is a fixed module list.
-    $this->assertIdentical(module_list(), array($module => $module));
+    $this->assertIdentical(array_keys(drupal_container()->get('module_handler')->getModuleList()), array($module));
     $this->assertIdentical(module_implements('permission'), array($module));
 
     // Verify that no modules have been installed.
@@ -54,9 +54,9 @@ function testEnableModulesLoad() {
 
     // Verify that the module does not exist yet.
     $this->assertFalse(module_exists($module), "$module module not found.");
-    $list = module_list();
-    $this->assertFalse(in_array($module, $list), "$module module in module_list() not found.");
-    $list = module_list('permission');
+    $list = array_keys(drupal_container()->get('module_handler')->getModuleList());
+    $this->assertFalse(in_array($module, $list), "$module module not found in the extension handler's module list.");
+    $list = module_implements('permission');
     $this->assertFalse(in_array($module, $list), "{$module}_permission() in module_implements() not found.");
 
     // Enable the module.
@@ -64,9 +64,9 @@ function testEnableModulesLoad() {
 
     // Verify that the module exists.
     $this->assertTrue(module_exists($module), "$module module found.");
-    $list = module_list();
-    $this->assertTrue(in_array($module, $list), "$module module in module_list() found.");
-    $list = module_list('permission');
+    $list = array_keys(drupal_container()->get('module_handler')->getModuleList());
+    $this->assertTrue(in_array($module, $list), "$module module found in the extension handler's module list.");
+    $list = module_implements('permission');
     $this->assertTrue(in_array($module, $list), "{$module}_permission() in module_implements() found.");
   }
 
@@ -83,9 +83,9 @@ function testEnableModulesInstall() {
 
     // Verify that the module does not exist yet.
     $this->assertFalse(module_exists($module), "$module module not found.");
-    $list = module_list();
-    $this->assertFalse(in_array($module, $list), "$module module in module_list() not found.");
-    $list = module_list('permission');
+    $list = array_keys(drupal_container()->get('module_handler')->getModuleList());
+    $this->assertFalse(in_array($module, $list), "$module module not found in the extension handler's module list.");
+    $list = module_implements('permission');
     $this->assertFalse(in_array($module, $list), "{$module}_permission() in module_implements() not found.");
 
     $this->assertFalse(db_table_exists($table), "'$table' database table not found.");
@@ -97,9 +97,9 @@ function testEnableModulesInstall() {
 
     // Verify that the enabled module exists.
     $this->assertTrue(module_exists($module), "$module module found.");
-    $list = module_list();
-    $this->assertTrue(in_array($module, $list), "$module module in module_list() found.");
-    $list = module_list('permission');
+    $list = array_keys(drupal_container()->get('module_handler')->getModuleList());
+    $this->assertTrue(in_array($module, $list), "$module module found in the extension handler's module list.");
+    $list = module_implements('permission');
     $this->assertTrue(in_array($module, $list), "{$module}_permission() in module_implements() found.");
 
     $this->assertTrue(db_table_exists($table), "'$table' database table found.");
@@ -180,4 +180,68 @@ function testInstallSchema() {
     $this->assertTrue($schema, "'$table' table schema found.");
   }
 
+  /**
+   * Tests that the fixed module list is retained after enabling and installing modules.
+   */
+  function testEnableModulesFixedList() {
+    // entity_test is loaded via $modules; its entity type should exist.
+    $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
+    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+
+    // Load some additional modules; entity_test should still exist.
+    $this->enableModules(array('entity', 'field', 'field_sql_storage', 'text', 'entity_test'), FALSE);
+    $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
+    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+
+    // Install some other modules; entity_test should still exist.
+    module_enable(array('field', 'field_sql_storage', 'field_test'), FALSE);
+    $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
+    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+
+    // Disable one of those modules; entity_test should still exist.
+    module_disable(array('field_test'));
+    $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
+    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+
+    // Set the weight of a module; entity_test should still exist.
+    module_set_weight('entity', -1);
+    $this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
+    $this->assertTrue(TRUE == entity_get_info('entity_test'));
+
+    // Reactivate the disabled module without enabling it.
+    $this->enableModules(array('field_test'), FALSE);
+
+    // Create a field and an instance.
+    $display = entity_create('entity_display', array(
+      'targetEntityType' => 'entity_test',
+      'bundle' => 'entity_test',
+      'viewMode' => 'default',
+    ));
+    $field = array(
+      'field_name' => 'test_field',
+      'type' => 'test_field'
+    );
+    field_create_field($field);
+    $instance = array(
+      'field_name' => $field['field_name'],
+      'entity_type' => 'entity_test',
+      'bundle' => 'entity_test',
+    );
+    field_create_instance($instance);
+  }
+
+  /**
+   * Tests that theme() works right after loading a module.
+   */
+  function testEnableModulesTheme() {
+    $element = array(
+      '#type' => 'container',
+      '#markup' => 'Foo',
+      '#attributes' => array(),
+    );
+    $this->enableModules(array('system'), FALSE);
+    // theme() throws an exception if modules are not loaded yet.
+    drupal_render($element);
+  }
+
 }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
index 648aa4251708..d7bd2253bd03 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/UnitTestBase.php
@@ -56,9 +56,6 @@ protected function setUp() {
     $conf = array();
     drupal_static_reset();
 
-    // Enforce an empty module list.
-    module_list(NULL, array());
-
     $conf['file_public_path'] = $this->public_files_directory;
 
     // Change the database prefix.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendUnitTest.php
index f301257b0574..525de36635a4 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendUnitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Cache/DatabaseBackendUnitTest.php
@@ -36,6 +36,11 @@ protected function createCacheBackend($bin) {
    * Installs system schema.
    */
   public function setUpCacheBackend() {
+    // Calling drupal_install_schema() entails a call to module_invoke, for which
+    // we need a ModuleHandler. Register one to the container.
+    // @todo Use DrupalUnitTestBase.
+    $this->container->register('module_handler', 'Drupal\Core\Extension\ModuleHandler');
+
     drupal_install_schema('system');
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php
index e8602f4e9701..2221aafe66f0 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Module/EnableDisableTest.php
@@ -43,7 +43,7 @@ function testEnableDisable() {
 
     // Remove already enabled modules (via installation profile).
     // @todo Remove this after removing all dependencies from Testing profile.
-    foreach (module_list() as $dependency) {
+    foreach ($this->container->get('module_handler')->getModuleList() as $dependency => $filename) {
       // Exclude required modules. Only installation profile "suggestions" can
       // be disabled and uninstalled.
       if (isset($modules[$dependency])) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleApiTest.php b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleApiTest.php
index 85a41841d952..64fc8e372f97 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleApiTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleApiTest.php
@@ -25,7 +25,7 @@ public static function getInfo() {
   }
 
   /**
-   * The basic functionality of module_list().
+   * The basic functionality of retrieving enabled modules.
    */
   function testModuleList() {
     // Build a list of modules, sorted alphabetically.
@@ -36,8 +36,8 @@ function testModuleList() {
     $module_list[] = 'standard';
 
     sort($module_list);
-    // Compare this list to the one returned by module_list(). We expect them
-    // to match, since all default profile modules have a weight equal to 0
+    // Compare this list to the one returned by the extension handler. We expect
+    // them to match, since all default profile modules have a weight equal to 0
     // (except for block.module, which has a lower weight but comes first in
     // the alphabet anyway).
     $this->assertModuleList($module_list, t('Standard profile'));
@@ -50,8 +50,7 @@ function testModuleList() {
 
     // Try to mess with the module weights.
     module_set_weight('contact', 20);
-    // Reset the module list.
-    system_list_reset();
+
     // Move contact to the end of the array.
     unset($module_list[array_search('contact', $module_list)]);
     $module_list[] = 'contact';
@@ -59,27 +58,26 @@ function testModuleList() {
 
     // Test the fixed list feature.
     $fixed_list = array(
-      'system' => array('filename' => drupal_get_path('module', 'system')),
-      'menu' => array('filename' => drupal_get_path('module', 'menu')),
+      'system' => 'core/modules/system/system.module',
+      'menu' => 'core/modules/menu/menu.module',
     );
-    module_list(NULL, $fixed_list);
+    $this->container->get('module_handler')->setModuleList($fixed_list);
     $new_module_list = array_combine(array_keys($fixed_list), array_keys($fixed_list));
     $this->assertModuleList($new_module_list, t('When using a fixed list'));
 
-    // Reset the module list.
-    module_list_reset();
-    $this->assertModuleList($module_list, t('After reset'));
   }
 
   /**
-   * Assert that module_list() return the expected values.
+   * Assert that the extension handler returns the expected values.
    *
    * @param $expected_values
    *   The expected values, sorted by weight and module name.
    */
   protected function assertModuleList(Array $expected_values, $condition) {
-    $expected_values = array_combine($expected_values, $expected_values);
-    $this->assertEqual($expected_values, module_list(), format_string('@condition: module_list() returns correct results', array('@condition' => $condition)));
+    $expected_values = array_values(array_unique($expected_values));
+    $enabled_modules = array_keys($this->container->get('module_handler')->getModuleList());
+    $enabled_modules = sort($enabled_modules);
+    $this->assertEqual($expected_values, $enabled_modules, format_string('@condition: extension handler returns correct results', array('@condition' => $condition)));
   }
 
   /**
@@ -103,12 +101,11 @@ function testModuleImplements() {
     // already loaded when the cache is rebuilt.
     // For that activate the module_test which provides the file to load.
     module_enable(array('module_test'));
-
+    $module_handler = drupal_container()->get('module_handler');
+    $module_handler->loadAll();
     module_load_include('inc', 'module_test', 'module_test.file');
-    $modules = module_implements('test_hook');
-    $static = drupal_static('module_implements');
+    $modules = $module_handler->getImplementations('test_hook');
     $this->assertTrue(in_array('module_test', $modules), 'Hook found.');
-    $this->assertEqual($static['test_hook']['module_test'], 'file', 'Include file detected.');
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php
index 916555d4817c..7804b0a486aa 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleTestBase.php
@@ -57,6 +57,7 @@ function assertTableCount($base_table, $count = TRUE) {
    *   The name of the module.
    */
   function assertModuleTablesExist($module) {
+    $this->rebuildContainer();
     $tables = array_keys(drupal_get_schema_unprocessed($module));
     $tables_exist = TRUE;
     foreach ($tables as $table) {
@@ -144,7 +145,7 @@ function assertNoModuleConfig($module) {
    *   Expected module state.
    */
   function assertModules(array $modules, $enabled) {
-    system_list_reset();
+    $this->rebuildContainer();
     foreach ($modules as $module) {
       if ($enabled) {
         $message = 'Module "@module" is enabled.';
@@ -152,7 +153,7 @@ function assertModules(array $modules, $enabled) {
       else {
         $message = 'Module "@module" is not enabled.';
       }
-      $this->assertEqual(module_exists($module), $enabled, format_string($message, array('@module' => $module)));
+      $this->assertEqual($this->container->get('module_handler')->moduleExists($module), $enabled, format_string($message, array('@module' => $module)));
     }
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/MainContentFallbackTest.php b/core/modules/system/lib/Drupal/system/Tests/System/MainContentFallbackTest.php
index 2de5ad346ea7..4e79ae92c3b3 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/MainContentFallbackTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/MainContentFallbackTest.php
@@ -56,7 +56,7 @@ function testMainContentFallback() {
     $edit['modules[Core][block][enable]'] = FALSE;
     $this->drupalPost('admin/modules', $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
-    system_list_reset();
+    $this->rebuildContainer();
     $this->assertFalse(module_exists('block'), 'Block module disabled.');
 
     // At this point, no region is filled and fallback should be triggered.
@@ -90,7 +90,7 @@ function testMainContentFallback() {
     $edit['modules[Core][block][enable]'] = 'block';
     $this->drupalPost('admin/modules', $edit, t('Save configuration'));
     $this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
-    system_list_reset();
+    $this->rebuildContainer();
     $this->assertTrue(module_exists('block'), 'Block module re-enabled.');
   }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/RegistryTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/RegistryTest.php
index 7ea187f572df..5ab43ca6c5f6 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/RegistryTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/RegistryTest.php
@@ -40,7 +40,7 @@ function testRaceCondition() {
 
     // Directly instantiate the theme registry, this will cause a base cache
     // entry to be written in __construct().
-    $registry = new ThemeRegistry($cid, 'cache', array('theme_registry' => TRUE));
+    $registry = new ThemeRegistry($cid, 'cache', array('theme_registry' => TRUE), $this->container->get('module_handler')->isLoaded());
 
     $this->assertTrue(cache()->get($cid), 'Cache entry was created.');
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
index 5267300f0695..6e9b383a2156 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
@@ -175,9 +175,19 @@ function testRegistryRebuild() {
     $this->assertIdentical(theme('theme_test_foo', array('foo' => 'a')), 'a', 'The theme registry contains theme_test_foo.');
 
     module_disable(array('theme_test'), FALSE);
+    // After enabling/disabling a module during a test, we need to rebuild the
+    // container and ensure the extension handler is loaded, otherwise theme()
+    // throws an exception.
+    $this->rebuildContainer();
+    $this->container->get('module_handler')->loadAll();
     $this->assertIdentical(theme('theme_test_foo', array('foo' => 'b')), '', 'The theme registry does not contain theme_test_foo, because the module is disabled.');
 
     module_enable(array('theme_test'), FALSE);
+    // After enabling/disabling a module during a test, we need to rebuild the
+    // container and ensure the extension handler is loaded, otherwise theme()
+    // throws an exception.
+    $this->rebuildContainer();
+    $this->container->get('module_handler')->loadAll();
     $this->assertIdentical(theme('theme_test_foo', array('foo' => 'c')), 'c', 'The theme registry contains theme_test_foo again after re-enabling the module.');
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalUpgradePathTest.php
index 8d4da5197da0..499acf28e73e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BareMinimalUpgradePathTest.php
@@ -82,7 +82,7 @@ public function testBasicMinimalUpgrade() {
     $this->assertFalse($result, 'No {menu_links} entry exists for user/autocomplete');
 
     // Verify that all required modules are enabled.
-    $enabled = module_list();
+    $enabled = $this->container->get('module_handler')->getModuleList();
     $required = array_filter(system_rebuild_module_data(), function ($data) {
       return !empty($data->info['required']);
     });
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/ModulesDisabledUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ModulesDisabledUpgradePathTest.php
index c85c912aa424..b02d5ffd79b0 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/ModulesDisabledUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ModulesDisabledUpgradePathTest.php
@@ -37,7 +37,7 @@ public function testDisabledUpgrade() {
     $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.');
 
     // Get enabled modules.
-    $enabled = module_list();
+    $enabled = drupal_container()->get('module_handler')->getModuleList();
     // Get all available modules.
     $available = system_rebuild_module_data();
     // Filter out hidden test modules.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/SystemUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/SystemUpgradePathTest.php
index 2384f58b3c3a..2929e71c145a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/SystemUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/SystemUpgradePathTest.php
@@ -115,8 +115,6 @@ public function testVariableUpgrade() {
   public function testFrontpageUpgrade() {
     $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.');
 
-    // Reset the module enable list to get the current result.
-    module_list_reset();
     $this->assertTrue(module_exists('views'), 'Views is enabled after the upgrade.');
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
index 106d726a8b5f..c4dda65035cb 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -268,9 +268,8 @@ protected function performUpgrade($register_errors = TRUE) {
 
     // Reload module list for modules that are enabled in the test database
     // but not on the test client.
-    system_list_reset();
-    module_implements_reset();
-    module_load_all(FALSE, TRUE);
+    drupal_container()->get('module_handler')->resetImplementations();
+    drupal_container()->get('module_handler')->reload();
 
     // Rebuild the container and all caches.
     $this->rebuildContainer();
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index c3c2ce4645c2..37a2bf23fb00 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -1204,7 +1204,7 @@ function system_modules_submit($form, &$form_state) {
 
   // Gets list of modules prior to install process, unsets $form_state['storage']
   // so we don't get redirected back to the confirmation form.
-  $pre_install_list = module_list();
+  $pre_install_list = drupal_container()->get('module_handler')->getModuleList();
   unset($form_state['storage']);
 
   // Reverse the 'enable' list, to order dependencies before dependents.
@@ -1216,7 +1216,7 @@ function system_modules_submit($form, &$form_state) {
 
   // Gets module list after install process, flushes caches and displays a
   // message if there are changes.
-  $post_install_list = module_list();
+  $post_install_list = drupal_container()->get('module_handler')->getModuleList();
   if ($pre_install_list != $post_install_list) {
     drupal_flush_all_caches();
     drupal_set_message(t('The configuration options have been saved.'));
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index c0e3fe61bf09..68dd42276fb7 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -400,7 +400,7 @@ function system_requirements($phase) {
     );
 
     // Check installed modules.
-    foreach (module_list() as $module) {
+    foreach (drupal_container()->get('module_handler')->getModuleList() as $module => $filename) {
       $updates = drupal_get_schema_versions($module);
       if ($updates !== FALSE) {
         $default = drupal_get_installed_schema_version($module);
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 336f84b5008d..17015c627607 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2777,7 +2777,7 @@ function system_get_info($type, $name = NULL) {
   $info = array();
   if ($type == 'module') {
     $data = system_rebuild_module_data();
-    foreach (module_list() as $module) {
+    foreach (drupal_container()->get('module_handler')->getModuleList() as $module => $filename) {
       $info[$module] = $data[$module]->info;
     }
   }
@@ -2920,7 +2920,7 @@ function system_rebuild_module_data() {
       $record->schema_version = SCHEMA_UNINSTALLED;
       $files[$module] = $record->filename;
     }
-    $modules = _module_build_dependencies($modules);
+    $modules = drupal_container()->get('module_handler')->buildModuleDependencies($modules);
     $modules_cache = $modules;
 
     // Store filenames to allow system_list() and drupal_get_filename() to
diff --git a/core/scripts/dump-database-d6.sh b/core/scripts/dump-database-d6.sh
index d39bb4306d83..106568d86d07 100644
--- a/core/scripts/dump-database-d6.sh
+++ b/core/scripts/dump-database-d6.sh
@@ -44,7 +44,7 @@
 
 ENDOFHEADER;
 
-foreach (module_list() as $module) {
+foreach (drupal_container()->get('module_handler')->getModuleList() as $module => $filename) {
   $output .= " *  - $module\n";
 }
 $output .= " */\n\n";
diff --git a/core/scripts/dump-database-d7.sh b/core/scripts/dump-database-d7.sh
index 3440fa25a758..3b8597dde9ee 100644
--- a/core/scripts/dump-database-d7.sh
+++ b/core/scripts/dump-database-d7.sh
@@ -45,7 +45,7 @@
 
 ENDOFHEADER;
 
-foreach (module_list() as $module) {
+foreach (drupal_container()->get('module_handler')->getModuleList() as $module => $filename) {
   $output .= " *  - $module\n";
 }
 $output .= " */\n\n";
diff --git a/core/update.php b/core/update.php
index 17b44fd096a0..1f7ac5f25ee4 100644
--- a/core/update.php
+++ b/core/update.php
@@ -333,10 +333,15 @@ function update_access_allowed() {
   // Calls to user_access() might fail during the Drupal 6 to 7 update process,
   // so we fall back on requiring that the user be logged in as user #1.
   try {
-    require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'user') . '/user.module';
+    $module_handler = drupal_container()->get('module_handler');
+    $module_filenames = $module_handler->getModuleList();
+    $module_filenames['user'] = 'core/modules/user/user.module';
+    $module_handler->setModuleList($module_filenames);
+    $module_handler->reload();
+    drupal_container()->get('kernel')->updateModules($module_filenames, $module_filenames);
     return user_access('administer software updates');
   }
-  catch (Exception $e) {
+  catch (\Exception $e) {
     return ($user->uid == 1);
   }
 }
@@ -435,13 +440,14 @@ function update_check_requirements($skip_warnings = FALSE) {
 
   // Load module basics.
   include_once DRUPAL_ROOT . '/core/includes/module.inc';
-  $module_list['system']['filename'] = 'core/modules/system/system.module';
-  module_list(NULL, $module_list);
-  drupal_load('module', 'system');
+  $module_list['system'] = 'core/modules/system/system.module';
+  $module_handler = drupal_container()->get('module_handler');
+  $module_handler->setModuleList($module_list);
+  $module_handler->load('system');
 
-  // Reset the module_implements() cache so that any new hook implementations
+  // Reset the module implementations cache so that any new hook implementations
   // in updated code are picked up.
-  module_implements_reset();
+  $module_handler->resetImplementations();
 
   // Set up $language, since the installer components require it.
   drupal_language_initialize();
-- 
GitLab