Commit 826245d5 authored by alexpott's avatar alexpott

Issue #2324055 by dawehner, cilefen, znerol: Split up the module manager into...

Issue #2324055 by dawehner, cilefen, znerol: Split up the module manager into runtime information and extension information
parent b6c1c2fb
......@@ -288,7 +288,10 @@ services:
arguments: ['@container.namespaces', '@cache.discovery', '@module_handler']
module_handler:
class: Drupal\Core\Extension\ModuleHandler
arguments: ['@app.root', '%container.modules%', '@kernel', '@cache.bootstrap']
arguments: ['@app.root', '%container.modules%', '@cache.bootstrap']
module_installer:
class: Drupal\Core\Extension\ModuleInstaller
arguments: ['@app.root', '@module_handler', '@kernel']
theme_handler:
class: Drupal\Core\Extension\ThemeHandler
arguments: ['@app.root', '@config.factory', '@module_handler', '@state', '@info_parser', '@logger.channel.default', '@asset.css.collection_optimizer', '@config.installer', '@config.manager', '@router.builder_indicator']
......
......@@ -978,7 +978,7 @@ function install_base_system(&$install_state) {
// Enable the user module so that sessions can be recorded during the
// upcoming bootstrap step.
\Drupal::moduleHandler()->install(array('user'), FALSE);
\Drupal::service('module_installer')->install(array('user'), FALSE);
// Save the list of other modules to install for the upcoming tasks.
// State can be set to the database now that system.module is installed.
......@@ -1797,7 +1797,7 @@ function _install_module_batch($module, $module_name, &$context) {
// loaded by drupal_bootstrap in subsequent batch requests, and other
// modules possibly depending on it can safely perform their installation
// steps.
\Drupal::moduleHandler()->install(array($module), FALSE);
\Drupal::service('module_installer')->install(array($module), FALSE);
$context['results'][] = $module;
$context['message'] = t('Installed %module module.', array('%module' => $module_name));
}
......
......@@ -615,7 +615,7 @@ function drupal_install_system($install_state) {
\Drupal::service('config.installer')->installDefaultConfig('core', 'core');
// Install System module and rebuild the newly available routes.
$kernel->getContainer()->get('module_handler')->install(array('system'), FALSE);
$kernel->getContainer()->get('module_installer')->install(array('system'), FALSE);
\Drupal::service('router.builder')->rebuild();
// Ensure default language is saved.
......
......@@ -157,10 +157,10 @@ function module_load_include($type, $module, $name = NULL) {
* @see \Drupal\Core\Extension\ModuleHandlerInterface::install()
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. Use
* \Drupal::moduleHandler()->install($module_list, $enable_dependencies = TRUE).
* \Drupal::service('module_installer')->install($module_list, $enable_dependencies = TRUE).
*/
function module_install($module_list, $enable_dependencies = TRUE) {
return \Drupal::moduleHandler()->install($module_list, $enable_dependencies);
return \Drupal::service('module_installer')->install($module_list, $enable_dependencies);
}
/**
......@@ -169,10 +169,10 @@ function module_install($module_list, $enable_dependencies = TRUE) {
* @see \Drupal\Core\Extension\ModuleHandlerInterface::module_install()
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. Use
* \Drupal::moduleHandler()->uninstall($module_list, $enable_dependencies = TRUE).
* \Drupal::service('module_installer')->uninstall($module_list, $enable_dependencies = TRUE).
*/
function module_uninstall($module_list = array(), $uninstall_dependents = TRUE) {
return \Drupal::moduleHandler()->uninstall($module_list, $uninstall_dependents);
return \Drupal::service('module_installer')->uninstall($module_list, $uninstall_dependents);
}
/**
......
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Config;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Component\Utility\String;
use Drupal\Core\Config\Entity\ImportableEntityStorageInterface;
......@@ -155,6 +156,13 @@ class ConfigImporter {
*/
protected $totalConfigurationToProcess = 0;
/**
* The module installer.
*
* @var \Drupal\Core\Extension\ModuleInstallerInterface
*/
protected $moduleInstaller;
/**
* Constructs a configuration import object.
*
......@@ -171,18 +179,21 @@ class ConfigImporter {
* The typed configuration manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler
* @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
* The module installer.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service.
*/
public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, TranslationInterface $string_translation) {
public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, TranslationInterface $string_translation) {
$this->storageComparer = $storage_comparer;
$this->eventDispatcher = $event_dispatcher;
$this->configManager = $config_manager;
$this->lock = $lock;
$this->typedConfigManager = $typed_config;
$this->moduleHandler = $module_handler;
$this->moduleInstaller = $module_installer;
$this->themeHandler = $theme_handler;
$this->stringTranslation = $string_translation;
foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
......@@ -750,7 +761,7 @@ protected function processExtension($type, $op, $name) {
->setSyncing(TRUE)
->setSourceStorage($this->storageComparer->getSourceStorage());
if ($type == 'module') {
$this->moduleHandler->$op(array($name), FALSE);
$this->moduleInstaller->$op(array($name), FALSE);
// Installing a module can cause a kernel boot therefore reinject all the
// services.
$this->reInjectMe();
......
......@@ -8,11 +8,9 @@
namespace Drupal\Core\Extension;
use Drupal\Component\Graph\Graph;
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\String;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DrupalKernelInterface;
/**
* Class that manages modules in a Drupal installation.
......@@ -64,13 +62,6 @@ class ModuleHandler implements ModuleHandlerInterface {
*/
protected $hookInfo;
/**
* The drupal kernel.
*
* @var \Drupal\Core\DrupalKernelInterface
*/
protected $kernel;
/**
* Cache backend for storing module hook implementation information.
*
......@@ -108,21 +99,18 @@ class ModuleHandler implements ModuleHandlerInterface {
* An associative array whose keys are the names of installed modules and
* whose values are Extension class parameters. This is normally the
* %container.modules% parameter being set up by DrupalKernel.
* @param \Drupal\Core\DrupalKernelInterface $kernel
* The drupal kernel.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend for storing module hook implementation information.
*
* @see \Drupal\Core\DrupalKernel
* @see \Drupal\Core\CoreServiceProvider
*/
public function __construct($root, array $module_list = array(), DrupalKernelInterface $kernel, CacheBackendInterface $cache_backend) {
public function __construct($root, array $module_list = array(), CacheBackendInterface $cache_backend) {
$this->root = $root;
$this->moduleList = array();
foreach ($module_list as $name => $module) {
$this->moduleList[$name] = new Extension($this->root, $module['type'], $module['pathname'], $module['filename']);
}
$this->kernel = $kernel;
$this->cacheBackend = $cache_backend;
}
......@@ -698,388 +686,6 @@ public static function parseDependency($dependency) {
return $value;
}
/**
* {@inheritdoc}
*/
public function install(array $module_list, $enable_dependencies = TRUE) {
$extension_config = \Drupal::config('core.extension');
if ($enable_dependencies) {
// Get all module data so we can find dependencies and sort.
$module_data = system_rebuild_module_data();
$module_list = $module_list ? array_combine($module_list, $module_list) : array();
if (array_diff_key($module_list, $module_data)) {
// One or more of the given modules doesn't exist.
return FALSE;
}
// Only process currently uninstalled modules.
$installed_modules = $extension_config->get('module') ?: array();
if (!$module_list = array_diff_key($module_list, $installed_modules)) {
// Nothing to do. All modules already installed.
return TRUE;
}
// Add dependencies to the list. The new modules will be processed as
// the while loop continues.
while (list($module) = each($module_list)) {
foreach (array_keys($module_data[$module]->requires) as $dependency) {
if (!isset($module_data[$dependency])) {
// The dependency does not exist.
return FALSE;
}
// Skip already installed modules.
if (!isset($module_list[$dependency]) && !isset($installed_modules[$dependency])) {
$module_list[$dependency] = $dependency;
}
}
}
// Set the actual module weights.
$module_list = array_map(function ($module) use ($module_data) {
return $module_data[$module]->sort;
}, $module_list);
// Sort the module list by their weights (reverse).
arsort($module_list);
$module_list = array_keys($module_list);
}
// Required for module installation checks.
include_once $this->root . '/core/includes/install.inc';
/** @var \Drupal\Core\Config\ConfigInstaller $config_installer */
$config_installer = \Drupal::service('config.installer');
$sync_status = $config_installer->isSyncing();
if ($sync_status) {
$source_storage = $config_installer->getSourceStorage();
}
$modules_installed = array();
foreach ($module_list as $module) {
$enabled = $extension_config->get("module.$module") !== NULL;
if (!$enabled) {
// Throw an exception if the module name is too long.
if (strlen($module) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
throw new ExtensionNameLengthException(format_string('Module name %name is over the maximum allowed length of @max characters.', array(
'%name' => $module,
'@max' => DRUPAL_EXTENSION_NAME_MAX_LENGTH,
)));
}
$extension_config
->set("module.$module", 0)
->set('module', module_config_sort($extension_config->get('module')))
->save();
// 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 = $this->getModuleList();
$current_modules = array_fill_keys(array_keys($current_module_filenames), 0);
$current_modules = module_config_sort(array_merge($current_modules, $extension_config->get('module')));
$module_filenames = array();
foreach ($current_modules as $name => $weight) {
if (isset($current_module_filenames[$name])) {
$module_filenames[$name] = $current_module_filenames[$name];
}
else {
$module_path = drupal_get_path('module', $name);
$pathname = "$module_path/$name.info.yml";
$filename = file_exists($module_path . "/$name.module") ? "$name.module" : NULL;
$module_filenames[$name] = new Extension($this->root, 'module', $pathname, $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.
$this->setModuleList($module_filenames);
$this->load($module);
module_load_install($module);
// Clear the static cache of system_rebuild_module_data() to pick up the
// new module, since it merges the installation status of modules into
// its statically cached list.
drupal_static_reset('system_rebuild_module_data');
// Update the kernel to include it.
$this->updateKernel($module_filenames);
// Refresh the schema to include it.
drupal_get_schema(NULL, TRUE);
// Allow modules to react prior to the installation of a module.
$this->invokeAll('module_preinstall', array($module));
// Now install the module's schema if necessary.
drupal_install_schema($module);
// Clear plugin manager caches and flag router to rebuild if requested.
\Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
\Drupal::service('router.builder_indicator')->setRebuildNeeded();
// Set the schema version to the number of the last update provided by
// the module, or the minimum core schema version.
$version = \Drupal::CORE_MINIMUM_SCHEMA_VERSION;
$versions = drupal_get_schema_versions($module);
if ($versions) {
$version = max(max($versions), $version);
}
// Notify interested components that this module's entity types are new.
// For example, a SQL-based storage handler can use this as an
// opportunity to create the necessary database tables.
// @todo Clean this up in https://www.drupal.org/node/2350111.
$entity_manager = \Drupal::entityManager();
foreach ($entity_manager->getDefinitions() as $entity_type) {
if ($entity_type->getProvider() == $module) {
$entity_manager->onEntityTypeCreate($entity_type);
}
}
// Install default configuration of the module.
$config_installer = \Drupal::service('config.installer');
if ($sync_status) {
$config_installer
->setSyncing(TRUE)
->setSourceStorage($source_storage);
}
else {
// If we're not in a config synchronisation reset the source storage
// so that the extension install storage will pick up the new
// configuration.
$config_installer->resetSourceStorage();
}
\Drupal::service('config.installer')->installDefaultConfig('module', $module);
// If the module has no current updates, but has some that were
// previously removed, set the version to the value of
// hook_update_last_removed().
if ($last_removed = $this->invoke($module, 'update_last_removed')) {
$version = max($version, $last_removed);
}
drupal_set_installed_schema_version($module, $version);
// Record the fact that it was installed.
$modules_installed[] = $module;
// file_get_stream_wrappers() needs to re-register Drupal's stream
// wrappers in case a module-provided stream wrapper is used later in
// the same request. In particular, this happens when installing Drupal
// via Drush, as the 'translations' stream wrapper is provided by
// Interface Translation module and is later used to import
// translations.
\Drupal::service('stream_wrapper_manager')->register();
// Update the theme registry to include it.
drupal_theme_rebuild();
// Modules can alter theme info, so refresh theme data.
// @todo ThemeHandler cannot be injected into ModuleHandler, since that
// causes a circular service dependency.
// @see https://drupal.org/node/2208429
\Drupal::service('theme_handler')->refreshInfo();
// Allow the module to perform install tasks.
$this->invoke($module, 'install');
// Record the fact that it was installed.
\Drupal::logger('system')->info('%module module installed.', array('%module' => $module));
}
}
// If any modules were newly installed, invoke hook_modules_installed().
if (!empty($modules_installed)) {
$this->invokeAll('modules_installed', array($modules_installed));
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
// Get all module data so we can find dependencies and sort.
$module_data = system_rebuild_module_data();
$module_list = $module_list ? array_combine($module_list, $module_list) : array();
if (array_diff_key($module_list, $module_data)) {
// One or more of the given modules doesn't exist.
return FALSE;
}
// Only process currently installed modules.
$extension_config = \Drupal::config('core.extension');
$installed_modules = $extension_config->get('module') ?: array();
if (!$module_list = array_intersect_key($module_list, $installed_modules)) {
// Nothing to do. All modules already uninstalled.
return TRUE;
}
if ($uninstall_dependents) {
// Add dependent modules to the list. The new modules will be processed as
// the while loop continues.
$profile = drupal_get_profile();
while (list($module) = each($module_list)) {
foreach (array_keys($module_data[$module]->required_by) as $dependent) {
if (!isset($module_data[$dependent])) {
// The dependent module does not exist.
return FALSE;
}
// Skip already uninstalled modules.
if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent]) && $dependent != $profile) {
$module_list[$dependent] = $dependent;
}
}
}
}
// Set the actual module weights.
$module_list = array_map(function ($module) use ($module_data) {
return $module_data[$module]->sort;
}, $module_list);
// Sort the module list by their weights.
asort($module_list);
$module_list = array_keys($module_list);
// 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.
$schema_store = \Drupal::keyValue('system.schema');
$entity_manager = \Drupal::entityManager();
foreach ($module_list as $module) {
// Clean up all entity bundles (including fields) of every entity type
// provided by the module that is being uninstalled.
// @todo Clean this up in https://www.drupal.org/node/2350111.
foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
if ($entity_type->getProvider() == $module) {
foreach (array_keys($entity_manager->getBundleInfo($entity_type_id)) as $bundle) {
$entity_manager->onBundleDelete($bundle, $entity_type_id);
}
}
}
// Allow modules to react prior to the uninstallation of a module.
$this->invokeAll('module_preuninstall', array($module));
// Uninstall the module.
module_load_install($module);
$this->invoke($module, 'uninstall');
// Remove all configuration belonging to the module.
\Drupal::service('config.manager')->uninstall('module', $module);
// Notify interested components that this module's entity types are being
// deleted. For example, a SQL-based storage handler can use this as an
// opportunity to drop the corresponding database tables.
// @todo Clean this up in https://www.drupal.org/node/2350111.
foreach ($entity_manager->getDefinitions() as $entity_type) {
if ($entity_type->getProvider() == $module) {
$entity_manager->onEntityTypeDelete($entity_type);
}
}
// Remove the schema.
drupal_uninstall_schema($module);
// Remove the module's entry from the config.
$extension_config->clear("module.$module")->save();
// Update the module handler to remove the module.
// The current ModuleHandler instance is obsolete with the kernel rebuild
// below.
$module_filenames = $this->getModuleList();
unset($module_filenames[$module]);
$this->setModuleList($module_filenames);
// Remove any potential cache bins provided by the module.
$this->removeCacheBins($module);
// Clear the static cache of system_rebuild_module_data() to pick up the
// new module, since it merges the installation status of modules into
// its statically cached list.
drupal_static_reset('system_rebuild_module_data');
// Clear plugin manager caches and flag router to rebuild if requested.
\Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
\Drupal::service('router.builder_indicator')->setRebuildNeeded();
// Update the kernel to exclude the uninstalled modules.
$this->updateKernel($module_filenames);
// Update the theme registry to remove the newly uninstalled module.
drupal_theme_rebuild();
// Modules can alter theme info, so refresh theme data.
// @todo ThemeHandler cannot be injected into ModuleHandler, since that
// causes a circular service dependency.
// @see https://drupal.org/node/2208429
\Drupal::service('theme_handler')->refreshInfo();
\Drupal::logger('system')->info('%module module uninstalled.', array('%module' => $module));
$schema_store->delete($module);
}
drupal_get_installed_schema_version(NULL, TRUE);
// Let other modules react.
$this->invokeAll('modules_uninstalled', array($module_list));
return TRUE;
}
/**
* Helper method for removing all cache bins registered by a given module.
*
* @param string $module
* The name of the module for which to remove all registered cache bins.
*/
protected function removeCacheBins($module) {
// Remove any cache bins defined by a module.
$service_yaml_file = drupal_get_path('module', $module) . "/$module.services.yml";
if (file_exists($service_yaml_file)) {
$definitions = Yaml::decode(file_get_contents($service_yaml_file));
if (isset($definitions['services'])) {
foreach ($definitions['services'] as $id => $definition) {
if (isset($definition['tags'])) {
foreach ($definition['tags'] as $tag) {
// This works for the default cache registration and even in some
// cases when a non-default "super" factory is used. That should
// be extremely rare.
if ($tag['name'] == 'cache.bin' && isset($definition['factory_service']) && isset($definition['factory_method']) && !empty($definition['arguments'])) {
try {
$factory = \Drupal::service($definition['factory_service']);
if (method_exists($factory, $definition['factory_method'])) {
$backend = call_user_func_array(array($factory, $definition['factory_method']), $definition['arguments']);
if ($backend instanceof CacheBackendInterface) {
$backend->removeBin();
}
}
}
catch (\Exception $e) {
watchdog_exception('system', $e, 'Failed to remove cache bin defined by the service %id.', array('%id' => $id));
}
}
}
}
}
}
}
}
/**
* {@inheritdoc}
*/
......@@ -1099,22 +705,4 @@ public function getName($module) {
return $module_data[$module]->info['name'];
}
/**
* Updates the kernel module list.
*
* @param string $module_filenames
* The list of installed modules.
*/
protected function updateKernel($module_filenames) {