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
Installs a given list of modules.

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();
    foreach ($module_list as $module) {
        // 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);
        }
        // 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) {
                // 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.
                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(),
                            ]);
                        }
                    }
                }
            }
        }
        // Install default configuration of the module.
        $config_installer = \Drupal::service('config.installer');
        $config_installer->installDefaultConfig('module', $module, DefaultConfigMode::InstallSimple);
        // 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.