*
* It registers the module in config, installs its own configuration,
* installs the schema, updates the Drupal kernel and more.
+ *
+ * We don't inject dependencies yet, as we would need to reload them after
+ * each installation or uninstallation of a module.
+ * https://www.drupal.org/project/drupal/issues/2350111 for example tries to
+ * solve this dilemma.
*/
class ModuleInstaller implements ModuleInstallerInterface {
}
// Add dependencies to the list. The new modules will be processed as
- // the while loop continues.
- while (list($module) = each($module_list)) {
+ // the foreach loop continues.
+ foreach ($module_list as $module => $value) {
foreach (array_keys($module_data[$module]->requires) as $dependency) {
if (!isset($module_data[$dependency])) {
// The dependency does not exist.
throw new ExtensionNameLengthException("Module name '$module' is over the maximum allowed length of " . DRUPAL_EXTENSION_NAME_MAX_LENGTH . ' characters');
}
+ // Load a new config object for each iteration, otherwise changes made
+ // in hook_install() are not reflected in $extension_config.
+ $extension_config = \Drupal::configFactory()->getEditable('core.extension');
+
// Check the validity of the default configuration. This will throw
// exceptions if the configuration is not valid.
$config_installer->checkConfigurationToInstall('module', $module);
$module_filenames[$name] = $current_module_filenames[$name];
}
else {
- $module_path = drupal_get_path('module', $name);
+ $module_path = \Drupal::service('extension.list.module')->getPath($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);
$this->moduleHandler->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');
+ // Clear the static cache of the "extension.list.module" service to pick
+ // up the new module, since it merges the installation status of modules
+ // into its statically cached list.
+ \Drupal::service('extension.list.module')->reset();
// Update the kernel to include it.
$this->updateKernel($module_filenames);
+ // Replace the route provider service with a version that will rebuild
+ // if routes used during installation. This ensures that a module's
+ // routes are available during installation. This has to occur before
+ // any services that depend on it are instantiated otherwise those
+ // services will have the old route provider injected. Note that, since
+ // the container is rebuilt by updating the kernel, the route provider
+ // service is the regular one even though we are in a loop and might
+ // have replaced it before.
+ \Drupal::getContainer()->set('router.route_provider.old', \Drupal::service('router.route_provider'));
+ \Drupal::getContainer()->set('router.route_provider', \Drupal::service('router.route_provider.lazy_builder'));
+
// Allow modules to react prior to the installation of a module.
$this->moduleHandler->invokeAll('module_preinstall', [$module]);
// @see https://www.drupal.org/node/2208429
\Drupal::service('theme_handler')->refreshInfo();
- // In order to make uninstalling transactional if anything uses routes.
- \Drupal::getContainer()->set('router.route_provider.old', \Drupal::service('router.route_provider'));
- \Drupal::getContainer()->set('router.route_provider', \Drupal::service('router.route_provider.lazy_builder'));
-
// Allow the module to perform install tasks.
$this->moduleHandler->invoke($module, 'install');
// If any modules were newly installed, invoke hook_modules_installed().
if (!empty($modules_installed)) {
- \Drupal::getContainer()->set('router.route_provider', \Drupal::service('router.route_provider.old'));
+ // If the container was rebuilt during hook_install() it might not have
+ // the 'router.route_provider.old' service.
+ if (\Drupal::hasService('router.route_provider.old')) {
+ \Drupal::getContainer()->set('router.route_provider', \Drupal::service('router.route_provider.old'));
+ }
if (!\Drupal::service('router.route_provider.lazy_builder')->hasRebuilt()) {
// Rebuild routes after installing module. This is done here on top of
// \Drupal\Core\Routing\RouteBuilder::destruct to not run into errors on
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)) {
+ // the foreach loop continues.
+ foreach ($module_list as $module => $value) {
foreach (array_keys($module_data[$module]->required_by) as $dependent) {
if (!isset($module_data[$dependent])) {
// The dependent module does not exist.
}
// Skip already uninstalled modules.
- if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent]) && $dependent != $profile) {
+ if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent])) {
$module_list[$dependent] = $dependent;
}
}
$update_manager->uninstallEntityType($entity_type);
}
elseif ($entity_type->entityClassImplements(FieldableEntityInterface::CLASS)) {
- // The module being installed may be adding new fields to existing
- // entity types. Field definitions for any entity type defined by
- // the module are handled in the if branch.
- $entity_type_id = $entity_type->id();
- /** @var \Drupal\Core\Entity\FieldableEntityStorageInterface $storage */
- $storage = $entity_manager->getStorage($entity_type_id);
- foreach ($entity_manager->getFieldStorageDefinitions($entity_type_id) as $storage_definition) {
- // @todo We need to trigger field purging here.
- // See https://www.drupal.org/node/2282119.
- if ($storage_definition->getProvider() == $module && !$storage->countFieldData($storage_definition, TRUE)) {
+ // The module being uninstalled might have added new fields to
+ // existing entity types. This will add them to the deleted fields
+ // repository so their data will be purged on cron.
+ foreach ($entity_manager->getFieldStorageDefinitions($entity_type->id()) as $storage_definition) {
+ if ($storage_definition->getProvider() == $module) {
$update_manager->uninstallFieldStorageDefinition($storage_definition);
}
}
// 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 the static cache of the "extension.list.module" service to pick
+ // up the new module, since it merges the installation status of modules
+ // into its statically cached list.
+ \Drupal::service('extension.list.module')->reset();
// Clear plugin manager caches.
\Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
* 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([$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.', ['%id' => $id]);
- }
- }
- }
+ if (!file_exists($service_yaml_file)) {
+ return;
+ }
+
+ $definitions = Yaml::decode(file_get_contents($service_yaml_file));
+
+ $cache_bin_services = array_filter(
+ isset($definitions['services']) ? $definitions['services'] : [],
+ function ($definition) {
+ $tags = isset($definition['tags']) ? $definition['tags'] : [];
+ foreach ($tags as $tag) {
+ if (isset($tag['name']) && ($tag['name'] == 'cache.bin')) {
+ return TRUE;
}
}
+ return FALSE;
+ }
+ );
+
+ foreach (array_keys($cache_bin_services) as $service_id) {
+ $backend = $this->kernel->getContainer()->get($service_id);
+ if ($backend instanceof CacheBackendInterface) {
+ $backend->removeBin();
}
}
}