function ModuleInstaller::doInstall

Installs a set of modules.

Parameters

array $module_list: The list of modules to install.

array $installed_modules: An array of the already installed modules.

bool $sync_status: The config sync status.

1 call to ModuleInstaller::doInstall()
ModuleInstaller::install in core/lib/Drupal/Core/Extension/ModuleInstaller.php

File

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

Class

ModuleInstaller
Default implementation of the module installer.

Namespace

Drupal\Core\Extension

Code

private function doInstall(array $module_list, array $installed_modules, bool $sync_status) : void {
  $extension_config = \Drupal::configFactory()->getEditable('core.extension');
  // Save this data without checking schema. This is a performance
  // improvement for module installation.
  $extension_config->set('module', module_config_sort(array_merge(array_fill_keys($module_list, 0), $installed_modules)))
    ->save(TRUE);
  // 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->moduleHandler
    ->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 = [];
  foreach ($current_modules as $name => $weight) {
    if (isset($current_module_filenames[$name])) {
      $module_filenames[$name] = $current_module_filenames[$name];
    }
    else {
      $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);
    }
  }
  // Update the module handler in order to have the correct module list
  // for the kernel update.
  $this->moduleHandler
    ->setModuleList($module_filenames);
  // 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);
  if (!InstallerKernel::installationAttempted()) {
    // Replace the route provider service with a version that will rebuild
    // if routes are 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'));
  }
  foreach ($module_list as $module) {
    // Load the module's .module and .install files. Do this for all modules
    // prior to calling hook_module_preinstall() in order to not pollute the
    // cache.
    $this->moduleHandler
      ->load($module);
    $this->moduleHandler
      ->loadInclude($module, 'install');
  }
  foreach ($module_list as $module) {
    // Allow modules to react prior to the installation of a module.
    $this->moduleHandler
      ->invokeAll('module_preinstall', [
      $module,
      $sync_status,
    ]);
    // Now install the module's schema if necessary.
    $this->installSchema($module);
  }
  // Clear plugin manager caches.
  // @todo should this be in the loop?
  \Drupal::getContainer()->get('plugin.cache_clearer')
    ->clearCachedDefinitions();
  $entity_type_providers_to_install = $module_list;
  foreach ($module_list as $module) {
    // Remove the module from the list of possible entity type providers to
    // install.
    array_shift($entity_type_providers_to_install);
    // Notify interested components that this module's entity types and
    // field storage definitions 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_type_manager = \Drupal::entityTypeManager();
    $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) {
      $is_fieldable_entity_type = $entity_type->entityClassImplements(FieldableEntityInterface::class);
      if ($entity_type->getProvider() == $module) {
        if ($is_fieldable_entity_type) {
          $update_manager->installFieldableEntityType($entity_type, $entity_field_manager->getFieldStorageDefinitions($entity_type->id()));
        }
        else {
          $update_manager->installEntityType($entity_type);
        }
      }
      elseif ($is_fieldable_entity_type && !in_array($entity_type->getProvider(), $entity_type_providers_to_install, TRUE)) {
        // The module being installed may be adding new fields to existing
        // entity types. Field definitions for any entity type defined by
        // modules being installed are handled in the if branch.
        foreach ($entity_field_manager->getFieldStorageDefinitions($entity_type->id()) as $storage_definition) {
          if ($storage_definition->getProvider() == $module) {
            // If the module being installed is also defining a storage key
            // for the entity type, the entity schema may not exist yet. It
            // will be created later in that case.
            try {
              $update_manager->installFieldStorageDefinition($storage_definition->getName(), $entity_type->id(), $module, $storage_definition);
            } catch (EntityStorageException $e) {
              Error::logException($this->logger, $e, 'An error occurred while notifying the creation of the @name field storage definition: "@message" in %function (line %line of %file).', [
                '@name' => $storage_definition->getName(),
              ]);
            }
          }
        }
      }
    }
  }
  foreach ($module_list as $module) {
    // Install default configuration of the module.
    $config_installer = \Drupal::service('config.installer');
    $config_installer->installDefaultConfig('module', $module, DefaultConfigMode::InstallSimple);
    // 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 = $this->updateRegistry
      ->getAvailableUpdates($module);
    if ($versions) {
      $version = max(max($versions), $version);
    }
    // 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);
    }
    $this->updateRegistry
      ->setInstalledVersion($module, $version);
  }
  // Drupal's stream wrappers needs to be re-registered 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::service('theme.registry')->reset();
  // 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();
  // Modules may provide single directory components which are added to
  // the core library definitions rather than the module itself, this
  // requires the library discovery cache to be rebuilt.
  \Drupal::service('library.discovery')->clear();
  $config_installer = \Drupal::service('config.installer');
  foreach ($module_list as $module) {
    // Create config entities a module has in the /install directory.
    $config_installer->installDefaultConfig('module', $module, DefaultConfigMode::InstallEntities);
    // Allow the module to perform install tasks.
    $this->invoke($module, 'install', [
      $sync_status,
    ]);
    // Record the fact that it was installed.
    \Drupal::logger('system')->info('%module module installed.', [
      '%module' => $module,
    ]);
  }
  // Install optional configuration from modules once all the modules have
  // been properly installed. This is often where soft dependencies lie.
  // @todo This code fixes \Drupal\Tests\help\Functional\HelpTest::testHelp().
  foreach ($module_list as $module) {
    $config_installer->installDefaultConfig('module', $module, DefaultConfigMode::Optional);
  }
  // Install optional configuration from other modules once all the modules
  // have been properly installed. This is often where soft dependencies lie.
  // @todo This code fixes
  //   \Drupal\Tests\forum\Functional\Module\DependencyTest::testUninstallDependents().
  foreach ($module_list as $module) {
    $config_installer->installDefaultConfig('module', $module, DefaultConfigMode::SiteOptional);
  }
  if (count($module_list) > 1) {
    // Reset the container so static caches are rebuilt. This prevents static
    // caches like those in \Drupal\views\ViewsData() from having stale data.
    // @todo Adding this code fixed
    //   \Drupal\KernelTests\Config\DefaultConfigTest::testModuleConfig().
    //   \Drupal\Component\DependencyInjection\Container::reset() seems to
    //   offer a way to do this but was broken for the following reasons:
    //   1. Needs to set itself to 'service_container' like the constructor.
    //   2. Needs to persist services, user and session like
    //      DrupalKernel::initializeContainer()
    //   3. Needs to work out how to work with things like
    //      KernelTestBase::register() which set synthetic like services.
    $this->updateKernel([]);
    // Refresh anything cached with core.extension. This prevents caches in
    // things like \Drupal\views\ViewsData() from having stale data.
    // @todo This fixes \Drupal\Tests\views\Functional\ViewsFormAlterTest().
    Cache::invalidateTags([
      'config:core.extension',
    ]);
  }
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.