Skip to content
Snippets Groups Projects
Commit 5efda3cf authored by Alex Pott's avatar Alex Pott
Browse files

Issue #2380293 by dawehner, willzyx, jibran, mpdonadio, alexpott, damiankloip,...

Issue #2380293 by dawehner, willzyx, jibran, mpdonadio, alexpott, damiankloip, cilefen, Berdir: Properly inject services into ModuleInstaller
parent d7b430a4
No related branches found
No related tags found
No related merge requests found
......@@ -396,6 +396,9 @@ services:
logger.channel.form:
parent: logger.channel_base
arguments: ['form']
logger.channel.system:
parent: logger.channel_base
arguments: ['system']
logger.log_message_parser:
class: Drupal\Core\Logger\LogMessageParser
......@@ -476,6 +479,7 @@ services:
arguments: ['@app.root', '%container.modules%', '@cache.bootstrap']
module_installer:
class: Drupal\Core\Extension\ModuleInstaller
arguments: ['@app.root', '@module_handler', '@kernel', '@config.factory', '@config.installer', '@plugin.cache_clearer', '@router.builder', '@stream_wrapper_manager', '@theme_handler', '@logger.channel.system', '@config.manager', '@keyvalue']
tags:
- { name: service_collector, tag: 'module_install.uninstall_validator', call: addUninstallValidator }
arguments: ['@app.root', '@module_handler', '@kernel', '@router.builder']
......
<?php
namespace Drupal\Core\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a trait which automatically updates dependencies from the container.
*
* Classes which trigger a container rebuild should point to the instances of
* the services with the new container. Calling the ::updateDependencies()
* method takes care of that.
*
* If the service depends on container parameters and if they can possibly
* change then the service will need to handle this itself.
*/
trait UpdateDependenciesTrait {
/**
* Updates an object's external dependencies from the container.
*
* This method depends on \Drupal\Core\DependencyInjection\Container::get()
* adding the _serviceId property to all services.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The container.
*
* @see \Drupal\Core\DependencyInjection\Container
*/
protected function updateDependencies(ContainerInterface $container) {
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value) && isset($value->_serviceId)) {
$this->$key = $container->get($value->_serviceId);
}
// Special case the container, which might not have a service ID.
elseif ($value instanceof ContainerInterface) {
$this->$key = $container;
}
}
}
}
......@@ -5,9 +5,18 @@
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ConfigInstallerInterface;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\DependencyInjection\UpdateDependenciesTrait;
use Drupal\Core\DrupalKernelInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\Core\Plugin\CachedDiscoveryClearerInterface;
use Drupal\Core\Routing\RouteBuilderInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManager;
use Psr\Log\LoggerInterface;
/**
* Default implementation of the module installer.
......@@ -17,6 +26,8 @@
*/
class ModuleInstaller implements ModuleInstallerInterface {
use UpdateDependenciesTrait;
/**
* The module handler.
*
......@@ -45,6 +56,69 @@ class ModuleInstaller implements ModuleInstallerInterface {
*/
protected $uninstallValidators;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The config installer.
*
* @var \Drupal\Core\Config\ConfigInstallerInterface
*/
protected $configInstaller;
/**
* The plugin cache clearer.
*
* @var \Drupal\Core\Plugin\CachedDiscoveryClearerInterface
*/
protected $pluginCacheClearer;
/**
* The route builder.
*
* @var \Drupal\Core\Routing\RouteBuilderInterface
*/
protected $routeBuilder;
/**
* The stream wrapper manager.
*
* @var \Drupal\Core\StreamWrapper\StreamWrapperManager
*/
protected $streamWrapperManager;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The config manager.
*
* @var \Drupal\Core\Config\ConfigManagerInterface
*/
protected $configManager;
/**
* The key value factory.
*
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
*/
protected $keyValueFactory;
/**
* Constructs a new ModuleInstaller instance.
*
......@@ -54,14 +128,41 @@ class ModuleInstaller implements ModuleInstallerInterface {
* The module handler.
* @param \Drupal\Core\DrupalKernelInterface $kernel
* The drupal kernel.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
* The config installer.
* @param \Drupal\Core\Plugin\CachedDiscoveryClearerInterface $plugin_cache_clearer
* The plugin cache clearer.
* @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
* The route builder.
* @param \Drupal\Core\StreamWrapper\StreamWrapperManager $stream_wrapper_manager
* The stream wrapper manager.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Psr\Log\LoggerInterface $logger
* The logger.
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
* The config manager.
* @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
* The key value factory.
*
* @see \Drupal\Core\DrupalKernel
* @see \Drupal\Core\CoreServiceProvider
*/
public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel) {
public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, CachedDiscoveryClearerInterface $plugin_cache_clearer, RouteBuilderInterface $route_builder, StreamWrapperManager $stream_wrapper_manager, ThemeHandlerInterface $theme_handler, LoggerInterface $logger, ConfigManagerInterface $config_manager, KeyValueFactoryInterface $key_value_factory) {
$this->root = $root;
$this->moduleHandler = $module_handler;
$this->kernel = $kernel;
$this->configFactory = $config_factory;
$this->configInstaller = $config_installer;
$this->pluginCacheClearer = $plugin_cache_clearer;
$this->routeBuilder = $route_builder;
$this->streamWrapperManager = $stream_wrapper_manager;
$this->themeHandler = $theme_handler;
$this->logger = $logger;
$this->configManager = $config_manager;
$this->keyValueFactory = $key_value_factory;
}
/**
......@@ -75,7 +176,7 @@ public function addUninstallValidator(ModuleUninstallValidatorInterface $uninsta
* {@inheritdoc}
*/
public function install(array $module_list, $enable_dependencies = TRUE) {
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
$extension_config = $this->configFactory->getEditable('core.extension');
if ($enable_dependencies) {
// Get all module data so we can find dependencies and sort.
$module_data = system_rebuild_module_data();
......@@ -121,11 +222,9 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
// 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();
$sync_status = $this->configInstaller->isSyncing();
if ($sync_status) {
$source_storage = $config_installer->getSourceStorage();
$source_storage = $this->configInstaller->getSourceStorage();
}
$modules_installed = array();
foreach ($module_list as $module) {
......@@ -138,7 +237,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
// Check the validity of the default configuration. This will throw
// exceptions if the configuration is not valid.
$config_installer->checkConfigurationToInstall('module', $module);
$this->configInstaller->checkConfigurationToInstall('module', $module);
// Save this data without checking schema. This is a performance
// improvement for module installation.
......@@ -197,7 +296,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
drupal_install_schema($module);
// Clear plugin manager caches.
\Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
$this->pluginCacheClearer->clearCachedDefinitions();
// Set the schema version to the number of the last update provided by
// the module, or the minimum core schema version.
......@@ -239,13 +338,12 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
}
// Install default configuration of the module.
$config_installer = \Drupal::service('config.installer');
if ($sync_status) {
$config_installer
$this->configInstaller
->setSyncing(TRUE)
->setSourceStorage($source_storage);
}
\Drupal::service('config.installer')->installDefaultConfig('module', $module);
$this->configInstaller->installDefaultConfig('module', $module);
// If the module has no current updates, but has some that were
// previously removed, set the version to the value of
......@@ -268,16 +366,13 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
// 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();
$this->streamWrapperManager->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://www.drupal.org/node/2208429
\Drupal::service('theme_handler')->refreshInfo();
$this->themeHandler->refreshInfo();
// In order to make uninstalling transactional if anything uses routes.
\Drupal::getContainer()->set('router.route_provider.old', \Drupal::service('router.route_provider'));
......@@ -287,7 +382,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
$this->moduleHandler->invoke($module, 'install');
// Record the fact that it was installed.
\Drupal::logger('system')->info('%module module installed.', array('%module' => $module));
$this->logger->info('%module module installed.', array('%module' => $module));
}
}
......@@ -320,7 +415,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
return FALSE;
}
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
$extension_config = $this->configFactory->getEditable('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.
......@@ -388,7 +483,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
$this->moduleHandler->invoke($module, 'uninstall');
// Remove all configuration belonging to the module.
\Drupal::service('config.manager')->uninstall('module', $module);
$this->configManager->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'));
......@@ -425,7 +520,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
// 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);
$this->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
......@@ -443,7 +538,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
drupal_static_reset('system_rebuild_module_data');
// Clear plugin manager caches.
\Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
$this->pluginCacheClearer->clearCachedDefinitions();
// Update the kernel to exclude the uninstalled modules.
$this->updateKernel($module_filenames);
......@@ -452,15 +547,11 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
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();
$this->themeHandler->refreshInfo();
\Drupal::logger('system')->info('%module module uninstalled.', array('%module' => $module));
$this->logger->info('%module module uninstalled.', array('%module' => $module));
$schema_store = \Drupal::keyValue('system.schema');
$schema_store->delete($module);
$this->keyValueFactory->get('system.schema')->delete($module);
/** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
$post_update_registry = \Drupal::service('update.post_update_registry');
......@@ -470,7 +561,7 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
// \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();
$this->routeBuilder->rebuild();
drupal_get_installed_schema_version(NULL, TRUE);
// Let other modules react.
......@@ -540,8 +631,7 @@ protected function updateKernel($module_filenames) {
$this->kernel->updateModules($module_filenames, $module_filenames);
// After rebuilding the container we need to update the injected
// dependencies.
$container = $this->kernel->getContainer();
$this->moduleHandler = $container->get('module_handler');
$this->updateDependencies($this->kernel->getContainer());
}
/**
......
<?php
namespace Drupal\Tests\Core\DependencyInjection;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\UpdateDependenciesTrait;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @coversDefaultClass \Drupal\Core\DependencyInjection\UpdateDependenciesTrait
* @group DependencyInjection
*/
class UpdateDependenciesTraitTest extends UnitTestCase {
/**
* @covers ::updateDependencies
*/
public function testUpdateDependencies() {
$service = new \StdClass();
$service->_serviceId = 'test_service';
$container = new ContainerBuilder();
$container->set('test_service', $service);
$trait_test = new UpdateDependenciesTraitTestClass();
$trait_test->dependency = clone $service;
$trait_test->container = $container;
$object = new \StdClass();
$trait_test->object = $object;
// Ensure that the service and container are updated but other objects are
// untouched.
$trait_test->updateDependenciesPublic($container);
$this->assertSame($service, $trait_test->dependency);
$this->assertSame($container, $trait_test->container);
$this->assertSame($object, $trait_test->object);
}
}
class UpdateDependenciesTraitTestClass {
use UpdateDependenciesTrait;
/**
* Updates dependencies.
*
* A wrapper around the protected UpdateDependenciesTrait::updateDependencies
* method.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
*/
public function updateDependenciesPublic(ContainerInterface $container) {
$this->updateDependencies($container);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment