You are here

public function ModuleInstaller::uninstall in Drupal 9

Same name in this branch
  1. 9 core/lib/Drupal/Core/Extension/ModuleInstaller.php \Drupal\Core\Extension\ModuleInstaller::uninstall()
  2. 9 core/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php \Drupal\Core\ProxyClass\Extension\ModuleInstaller::uninstall()
Same name and namespace in other branches
  1. 8 core/lib/Drupal/Core/Extension/ModuleInstaller.php \Drupal\Core\Extension\ModuleInstaller::uninstall()

Uninstalls a given list of modules.

Parameters

string[] $module_list: The modules to uninstall.

bool $uninstall_dependents: (optional) If TRUE, dependent modules will automatically be uninstalled in the correct order. This incurs a significant performance cost, so use FALSE if you know $module_list is already complete.

Return value

bool FALSE if one or more dependencies are missing, TRUE otherwise.

Throws

\Drupal\Core\Extension\ModuleUninstallValidatorException Thrown when validation prevented the module from being uninstalled.

Overrides ModuleInstallerInterface::uninstall

See also

hook_module_preuninstall()

hook_uninstall()

hook_modules_uninstalled()

File

core/lib/Drupal/Core/Extension/ModuleInstaller.php, line 395

Class

ModuleInstaller
Default implementation of the module installer.

Namespace

Drupal\Core\Extension

Code

public function uninstall(array $module_list, $uninstall_dependents = TRUE) {

  // Get all module data so we can find dependencies and sort.
  $module_data = \Drupal::service('extension.list.module')
    ->getList();
  $sync_status = \Drupal::service('config.installer')
    ->isSyncing();
  $module_list = $module_list ? array_combine($module_list, $module_list) : [];
  if (array_diff_key($module_list, $module_data)) {

    // One or more of the given modules doesn't exist.
    return FALSE;
  }
  $extension_config = \Drupal::configFactory()
    ->getEditable('core.extension');
  $installed_modules = $extension_config
    ->get('module') ?: [];
  if (!($module_list = array_intersect_key($module_list, $installed_modules))) {

    // Nothing to do. All modules already uninstalled.
    return TRUE;
  }
  if ($uninstall_dependents) {
    $theme_list = \Drupal::service('extension.list.theme')
      ->getList();

    // Add dependent modules to the list. The new modules will be processed as
    // 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]) && !isset($theme_list[$dependent])) {

          // The dependent module or theme does not exist.
          return FALSE;
        }

        // Skip already uninstalled modules.
        if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent])) {
          $module_list[$dependent] = $dependent;
        }
      }
    }
  }

  // Use the validators and throw an exception with the reasons.
  if ($reasons = $this
    ->validateUninstall($module_list)) {
    foreach ($reasons as $reason) {
      $reason_message[] = implode(', ', $reason);
    }
    throw new ModuleUninstallValidatorException('The following reasons prevent the modules from being uninstalled: ' . implode('; ', $reason_message));
  }

  // 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.
  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.
    $entity_type_manager = \Drupal::entityTypeManager();
    $entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');
    foreach ($entity_type_manager
      ->getDefinitions() as $entity_type_id => $entity_type) {
      if ($entity_type
        ->getProvider() == $module) {
        foreach (array_keys($entity_type_bundle_info
          ->getBundleInfo($entity_type_id)) as $bundle) {
          \Drupal::service('entity_bundle.listener')
            ->onBundleDelete($bundle, $entity_type_id);
        }
      }
    }

    // Allow modules to react prior to the uninstallation of a module.
    $this->moduleHandler
      ->invokeAll('module_preuninstall', [
      $module,
    ]);

    // Uninstall the module.
    module_load_install($module);
    $this->moduleHandler
      ->invoke($module, 'uninstall', [
      $sync_status,
    ]);

    // Remove all configuration belonging to the module.
    \Drupal::service('config.manager')
      ->uninstall('module', $module);

    // 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'));

    // 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.
    $update_manager = \Drupal::entityDefinitionUpdateManager();

    /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
    $entity_field_manager = \Drupal::service('entity_field.manager');
    foreach ($entity_type_manager
      ->getDefinitions() as $entity_type) {
      if ($entity_type
        ->getProvider() == $module) {
        $update_manager
          ->uninstallEntityType($entity_type);
      }
      elseif ($entity_type
        ->entityClassImplements(FieldableEntityInterface::CLASS)) {

        // 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_field_manager
          ->getFieldStorageDefinitions($entity_type
          ->id()) as $storage_definition) {
          if ($storage_definition
            ->getProvider() == $module) {
            $update_manager
              ->uninstallFieldStorageDefinition($storage_definition);
          }
        }
      }
    }

    // Remove the schema.
    $this
      ->uninstallSchema($module);

    // Remove the module's entry from the config. Don't check schema when
    // uninstalling a module since we are only clearing a key.
    \Drupal::configFactory()
      ->getEditable('core.extension')
      ->clear("module.{$module}")
      ->save(TRUE);

    // Update the module handler to remove the module.
    // The current ModuleHandler instance is obsolete with the kernel rebuild
    // below.
    $module_filenames = $this->moduleHandler
      ->getModuleList();
    unset($module_filenames[$module]);
    $this->moduleHandler
      ->setModuleList($module_filenames);

    // Remove any potential cache bins provided by the module.
    $this
      ->removeCacheBins($module);

    // 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 exclude the uninstalled modules.
    $this
      ->updateKernel($module_filenames);

    // Clear plugin manager caches.
    \Drupal::getContainer()
      ->get('plugin.cache_clearer')
      ->clearCachedDefinitions();

    // 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://www.drupal.org/node/2208429
    \Drupal::service('theme_handler')
      ->refreshInfo();
    \Drupal::logger('system')
      ->info('%module module uninstalled.', [
      '%module' => $module,
    ]);

    /** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
    $update_registry = \Drupal::service('update.update_hook_registry');
    $update_registry
      ->deleteInstalledVersion($module);

    /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
    $post_update_registry = \Drupal::service('update.post_update_registry');
    $post_update_registry
      ->filterOutInvokedUpdatesByModule($module);
  }

  // Rebuild routes after installing module. This is done here on top of
  // \Drupal\Core\Routing\RouteBuilder::destruct to not run into errors on
  // fastCGI which executes ::destruct() after the Module uninstallation page
  // was sent already.
  \Drupal::service('router.builder')
    ->rebuild();

  // Let other modules react.
  $this->moduleHandler
    ->invokeAll('modules_uninstalled', [
    $module_list,
    $sync_status,
  ]);

  // Flush all persistent caches.
  // Any cache entry might implicitly depend on the uninstalled modules,
  // so clear all of them explicitly.
  $this->moduleHandler
    ->invokeAll('cache_flush');
  foreach (Cache::getBins() as $service_id => $cache_backend) {
    $cache_backend
      ->deleteAll();
  }
  return TRUE;
}