ModuleInstaller.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. namespace Drupal\Core\Extension;
  3. use Drupal\Core\Cache\Cache;
  4. use Drupal\Core\Cache\CacheBackendInterface;
  5. use Drupal\Core\DrupalKernelInterface;
  6. use Drupal\Core\Entity\EntityStorageException;
  7. use Drupal\Core\Entity\FieldableEntityInterface;
  8. use Drupal\Core\Serialization\Yaml;
  9. /**
  10. * Default implementation of the module installer.
  11. *
  12. * It registers the module in config, installs its own configuration,
  13. * installs the schema, updates the Drupal kernel and more.
  14. *
  15. * We don't inject dependencies yet, as we would need to reload them after
  16. * each installation or uninstallation of a module.
  17. * https://www.drupal.org/project/drupal/issues/2350111 for example tries to
  18. * solve this dilemma.
  19. */
  20. class ModuleInstaller implements ModuleInstallerInterface {
  21. /**
  22. * The module handler.
  23. *
  24. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  25. */
  26. protected $moduleHandler;
  27. /**
  28. * The drupal kernel.
  29. *
  30. * @var \Drupal\Core\DrupalKernelInterface
  31. */
  32. protected $kernel;
  33. /**
  34. * The app root.
  35. *
  36. * @var string
  37. */
  38. protected $root;
  39. /**
  40. * The uninstall validators.
  41. *
  42. * @var \Drupal\Core\Extension\ModuleUninstallValidatorInterface[]
  43. */
  44. protected $uninstallValidators;
  45. /**
  46. * Constructs a new ModuleInstaller instance.
  47. *
  48. * @param string $root
  49. * The app root.
  50. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  51. * The module handler.
  52. * @param \Drupal\Core\DrupalKernelInterface $kernel
  53. * The drupal kernel.
  54. *
  55. * @see \Drupal\Core\DrupalKernel
  56. * @see \Drupal\Core\CoreServiceProvider
  57. */
  58. public function __construct($root, ModuleHandlerInterface $module_handler, DrupalKernelInterface $kernel) {
  59. $this->root = $root;
  60. $this->moduleHandler = $module_handler;
  61. $this->kernel = $kernel;
  62. }
  63. /**
  64. * {@inheritdoc}
  65. */
  66. public function addUninstallValidator(ModuleUninstallValidatorInterface $uninstall_validator) {
  67. $this->uninstallValidators[] = $uninstall_validator;
  68. }
  69. /**
  70. * {@inheritdoc}
  71. */
  72. public function install(array $module_list, $enable_dependencies = TRUE) {
  73. $extension_config = \Drupal::configFactory()->getEditable('core.extension');
  74. // Get all module data so we can find dependencies and sort and find the
  75. // core requirements. The module list needs to be reset so that it can
  76. // re-scan and include any new modules that may have been added directly
  77. // into the filesystem.
  78. $module_data = \Drupal::service('extension.list.module')->reset()->getList();
  79. foreach ($module_list as $module) {
  80. if (!empty($module_data[$module]->info['core_incompatible'])) {
  81. throw new MissingDependencyException("Unable to install modules: module '$module' is incompatible with this version of Drupal core.");
  82. }
  83. }
  84. if ($enable_dependencies) {
  85. $module_list = $module_list ? array_combine($module_list, $module_list) : [];
  86. if ($missing_modules = array_diff_key($module_list, $module_data)) {
  87. // One or more of the given modules doesn't exist.
  88. throw new MissingDependencyException(sprintf('Unable to install modules %s due to missing modules %s.', implode(', ', $module_list), implode(', ', $missing_modules)));
  89. }
  90. // Only process currently uninstalled modules.
  91. $installed_modules = $extension_config->get('module') ?: [];
  92. if (!$module_list = array_diff_key($module_list, $installed_modules)) {
  93. // Nothing to do. All modules already installed.
  94. return TRUE;
  95. }
  96. // Add dependencies to the list. The new modules will be processed as
  97. // the foreach loop continues.
  98. foreach ($module_list as $module => $value) {
  99. foreach (array_keys($module_data[$module]->requires) as $dependency) {
  100. if (!isset($module_data[$dependency])) {
  101. // The dependency does not exist.
  102. throw new MissingDependencyException("Unable to install modules: module '$module' is missing its dependency module $dependency.");
  103. }
  104. // Skip already installed modules.
  105. if (!isset($module_list[$dependency]) && !isset($installed_modules[$dependency])) {
  106. if ($module_data[$dependency]->info['core_incompatible']) {
  107. throw new MissingDependencyException("Unable to install modules: module '$module'. Its dependency module '$dependency' is incompatible with this version of Drupal core.");
  108. }
  109. $module_list[$dependency] = $dependency;
  110. }
  111. }
  112. }
  113. // Set the actual module weights.
  114. $module_list = array_map(function ($module) use ($module_data) {
  115. return $module_data[$module]->sort;
  116. }, $module_list);
  117. // Sort the module list by their weights (reverse).
  118. arsort($module_list);
  119. $module_list = array_keys($module_list);
  120. }
  121. // Required for module installation checks.
  122. include_once $this->root . '/core/includes/install.inc';
  123. /** @var \Drupal\Core\Config\ConfigInstaller $config_installer */
  124. $config_installer = \Drupal::service('config.installer');
  125. $sync_status = $config_installer->isSyncing();
  126. if ($sync_status) {
  127. $source_storage = $config_installer->getSourceStorage();
  128. }
  129. $modules_installed = [];
  130. foreach ($module_list as $module) {
  131. $enabled = $extension_config->get("module.$module") !== NULL;
  132. if (!$enabled) {
  133. // Throw an exception if the module name is too long.
  134. if (strlen($module) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
  135. throw new ExtensionNameLengthException("Module name '$module' is over the maximum allowed length of " . DRUPAL_EXTENSION_NAME_MAX_LENGTH . ' characters');
  136. }
  137. // Load a new config object for each iteration, otherwise changes made
  138. // in hook_install() are not reflected in $extension_config.
  139. $extension_config = \Drupal::configFactory()->getEditable('core.extension');
  140. // Check the validity of the default configuration. This will throw
  141. // exceptions if the configuration is not valid.
  142. $config_installer->checkConfigurationToInstall('module', $module);
  143. // Save this data without checking schema. This is a performance
  144. // improvement for module installation.
  145. $extension_config
  146. ->set("module.$module", 0)
  147. ->set('module', module_config_sort($extension_config->get('module')))
  148. ->save(TRUE);
  149. // Prepare the new module list, sorted by weight, including filenames.
  150. // This list is used for both the ModuleHandler and DrupalKernel. It
  151. // needs to be kept in sync between both. A DrupalKernel reboot or
  152. // rebuild will automatically re-instantiate a new ModuleHandler that
  153. // uses the new module list of the kernel. However, DrupalKernel does
  154. // not cause any modules to be loaded.
  155. // Furthermore, the currently active (fixed) module list can be
  156. // different from the configured list of enabled modules. For all active
  157. // modules not contained in the configured enabled modules, we assume a
  158. // weight of 0.
  159. $current_module_filenames = $this->moduleHandler->getModuleList();
  160. $current_modules = array_fill_keys(array_keys($current_module_filenames), 0);
  161. $current_modules = module_config_sort(array_merge($current_modules, $extension_config->get('module')));
  162. $module_filenames = [];
  163. foreach ($current_modules as $name => $weight) {
  164. if (isset($current_module_filenames[$name])) {
  165. $module_filenames[$name] = $current_module_filenames[$name];
  166. }
  167. else {
  168. $module_path = \Drupal::service('extension.list.module')->getPath($name);
  169. $pathname = "$module_path/$name.info.yml";
  170. $filename = file_exists($module_path . "/$name.module") ? "$name.module" : NULL;
  171. $module_filenames[$name] = new Extension($this->root, 'module', $pathname, $filename);
  172. }
  173. }
  174. // Update the module handler in order to have the correct module list
  175. // for the kernel update.
  176. $this->moduleHandler->setModuleList($module_filenames);
  177. // Clear the static cache of the "extension.list.module" service to pick
  178. // up the new module, since it merges the installation status of modules
  179. // into its statically cached list.
  180. \Drupal::service('extension.list.module')->reset();
  181. // Update the kernel to include it.
  182. $this->updateKernel($module_filenames);
  183. // Load the module's .module and .install files.
  184. $this->moduleHandler->load($module);
  185. module_load_install($module);
  186. // Replace the route provider service with a version that will rebuild
  187. // if routes used during installation. This ensures that a module's
  188. // routes are available during installation. This has to occur before
  189. // any services that depend on it are instantiated otherwise those
  190. // services will have the old route provider injected. Note that, since
  191. // the container is rebuilt by updating the kernel, the route provider
  192. // service is the regular one even though we are in a loop and might
  193. // have replaced it before.
  194. \Drupal::getContainer()->set('router.route_provider.old', \Drupal::service('router.route_provider'));
  195. \Drupal::getContainer()->set('router.route_provider', \Drupal::service('router.route_provider.lazy_builder'));
  196. // Allow modules to react prior to the installation of a module.
  197. $this->moduleHandler->invokeAll('module_preinstall', [$module]);
  198. // Now install the module's schema if necessary.
  199. drupal_install_schema($module);
  200. // Clear plugin manager caches.
  201. \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
  202. // Set the schema version to the number of the last update provided by
  203. // the module, or the minimum core schema version.
  204. $version = \Drupal::CORE_MINIMUM_SCHEMA_VERSION;
  205. $versions = drupal_get_schema_versions($module);
  206. if ($versions) {
  207. $version = max(max($versions), $version);
  208. }
  209. // Notify interested components that this module's entity types and
  210. // field storage definitions are new. For example, a SQL-based storage
  211. // handler can use this as an opportunity to create the necessary
  212. // database tables.
  213. // @todo Clean this up in https://www.drupal.org/node/2350111.
  214. $entity_type_manager = \Drupal::entityTypeManager();
  215. $update_manager = \Drupal::entityDefinitionUpdateManager();
  216. /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
  217. $entity_field_manager = \Drupal::service('entity_field.manager');
  218. foreach ($entity_type_manager->getDefinitions() as $entity_type) {
  219. $is_fieldable_entity_type = $entity_type->entityClassImplements(FieldableEntityInterface::class);
  220. if ($entity_type->getProvider() == $module) {
  221. if ($is_fieldable_entity_type) {
  222. $update_manager->installFieldableEntityType($entity_type, $entity_field_manager->getFieldStorageDefinitions($entity_type->id()));
  223. }
  224. else {
  225. $update_manager->installEntityType($entity_type);
  226. }
  227. }
  228. elseif ($is_fieldable_entity_type) {
  229. // The module being installed may be adding new fields to existing
  230. // entity types. Field definitions for any entity type defined by
  231. // the module are handled in the if branch.
  232. foreach ($entity_field_manager->getFieldStorageDefinitions($entity_type->id()) as $storage_definition) {
  233. if ($storage_definition->getProvider() == $module) {
  234. // If the module being installed is also defining a storage key
  235. // for the entity type, the entity schema may not exist yet. It
  236. // will be created later in that case.
  237. try {
  238. $update_manager->installFieldStorageDefinition($storage_definition->getName(), $entity_type->id(), $module, $storage_definition);
  239. }
  240. catch (EntityStorageException $e) {
  241. watchdog_exception('system', $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()]);
  242. }
  243. }
  244. }
  245. }
  246. }
  247. // Install default configuration of the module.
  248. $config_installer = \Drupal::service('config.installer');
  249. if ($sync_status) {
  250. $config_installer
  251. ->setSyncing(TRUE)
  252. ->setSourceStorage($source_storage);
  253. }
  254. \Drupal::service('config.installer')->installDefaultConfig('module', $module);
  255. // If the module has no current updates, but has some that were
  256. // previously removed, set the version to the value of
  257. // hook_update_last_removed().
  258. if ($last_removed = $this->moduleHandler->invoke($module, 'update_last_removed')) {
  259. $version = max($version, $last_removed);
  260. }
  261. drupal_set_installed_schema_version($module, $version);
  262. // Ensure that all post_update functions are registered already. This
  263. // should include existing post-updates, as well as any specified as
  264. // having been previously removed, to ensure that newly installed and
  265. // updated sites have the same entries in the registry.
  266. /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
  267. $post_update_registry = \Drupal::service('update.post_update_registry');
  268. $post_update_registry->registerInvokedUpdates(array_merge($post_update_registry->getModuleUpdateFunctions($module), array_keys($post_update_registry->getRemovedPostUpdates($module))));
  269. // Record the fact that it was installed.
  270. $modules_installed[] = $module;
  271. // Drupal's stream wrappers needs to be re-registered in case a
  272. // module-provided stream wrapper is used later in the same request. In
  273. // particular, this happens when installing Drupal via Drush, as the
  274. // 'translations' stream wrapper is provided by Interface Translation
  275. // module and is later used to import translations.
  276. \Drupal::service('stream_wrapper_manager')->register();
  277. // Update the theme registry to include it.
  278. drupal_theme_rebuild();
  279. // Modules can alter theme info, so refresh theme data.
  280. // @todo ThemeHandler cannot be injected into ModuleHandler, since that
  281. // causes a circular service dependency.
  282. // @see https://www.drupal.org/node/2208429
  283. \Drupal::service('theme_handler')->refreshInfo();
  284. // Allow the module to perform install tasks.
  285. $this->moduleHandler->invoke($module, 'install', [$sync_status]);
  286. // Record the fact that it was installed.
  287. \Drupal::logger('system')->info('%module module installed.', ['%module' => $module]);
  288. }
  289. }
  290. // If any modules were newly installed, invoke hook_modules_installed().
  291. if (!empty($modules_installed)) {
  292. // If the container was rebuilt during hook_install() it might not have
  293. // the 'router.route_provider.old' service.
  294. if (\Drupal::hasService('router.route_provider.old')) {
  295. \Drupal::getContainer()->set('router.route_provider', \Drupal::service('router.route_provider.old'));
  296. }
  297. if (!\Drupal::service('router.route_provider.lazy_builder')->hasRebuilt()) {
  298. // Rebuild routes after installing module. This is done here on top of
  299. // \Drupal\Core\Routing\RouteBuilder::destruct to not run into errors on
  300. // fastCGI which executes ::destruct() after the module installation
  301. // page was sent already.
  302. \Drupal::service('router.builder')->rebuild();
  303. }
  304. $this->moduleHandler->invokeAll('modules_installed', [$modules_installed, $sync_status]);
  305. }
  306. return TRUE;
  307. }
  308. /**
  309. * {@inheritdoc}
  310. */
  311. public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
  312. // Get all module data so we can find dependencies and sort.
  313. $module_data = \Drupal::service('extension.list.module')->getList();
  314. $sync_status = \Drupal::service('config.installer')->isSyncing();
  315. $module_list = $module_list ? array_combine($module_list, $module_list) : [];
  316. if (array_diff_key($module_list, $module_data)) {
  317. // One or more of the given modules doesn't exist.
  318. return FALSE;
  319. }
  320. $extension_config = \Drupal::configFactory()->getEditable('core.extension');
  321. $installed_modules = $extension_config->get('module') ?: [];
  322. if (!$module_list = array_intersect_key($module_list, $installed_modules)) {
  323. // Nothing to do. All modules already uninstalled.
  324. return TRUE;
  325. }
  326. if ($uninstall_dependents) {
  327. $theme_list = \Drupal::service('extension.list.theme')->getList();
  328. // Add dependent modules to the list. The new modules will be processed as
  329. // the foreach loop continues.
  330. foreach ($module_list as $module => $value) {
  331. foreach (array_keys($module_data[$module]->required_by) as $dependent) {
  332. if (!isset($module_data[$dependent]) && !isset($theme_list[$dependent])) {
  333. // The dependent module or theme does not exist.
  334. return FALSE;
  335. }
  336. // Skip already uninstalled modules.
  337. if (isset($installed_modules[$dependent]) && !isset($module_list[$dependent])) {
  338. $module_list[$dependent] = $dependent;
  339. }
  340. }
  341. }
  342. }
  343. // Use the validators and throw an exception with the reasons.
  344. if ($reasons = $this->validateUninstall($module_list)) {
  345. foreach ($reasons as $reason) {
  346. $reason_message[] = implode(', ', $reason);
  347. }
  348. throw new ModuleUninstallValidatorException('The following reasons prevent the modules from being uninstalled: ' . implode('; ', $reason_message));
  349. }
  350. // Set the actual module weights.
  351. $module_list = array_map(function ($module) use ($module_data) {
  352. return $module_data[$module]->sort;
  353. }, $module_list);
  354. // Sort the module list by their weights.
  355. asort($module_list);
  356. $module_list = array_keys($module_list);
  357. // Only process modules that are enabled. A module is only enabled if it is
  358. // configured as enabled. Custom or overridden module handlers might contain
  359. // the module already, which means that it might be loaded, but not
  360. // necessarily installed.
  361. foreach ($module_list as $module) {
  362. // Clean up all entity bundles (including fields) of every entity type
  363. // provided by the module that is being uninstalled.
  364. // @todo Clean this up in https://www.drupal.org/node/2350111.
  365. $entity_type_manager = \Drupal::entityTypeManager();
  366. $entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');
  367. foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) {
  368. if ($entity_type->getProvider() == $module) {
  369. foreach (array_keys($entity_type_bundle_info->getBundleInfo($entity_type_id)) as $bundle) {
  370. \Drupal::service('entity_bundle.listener')->onBundleDelete($bundle, $entity_type_id);
  371. }
  372. }
  373. }
  374. // Allow modules to react prior to the uninstallation of a module.
  375. $this->moduleHandler->invokeAll('module_preuninstall', [$module]);
  376. // Uninstall the module.
  377. module_load_install($module);
  378. $this->moduleHandler->invoke($module, 'uninstall', [$sync_status]);
  379. // Remove all configuration belonging to the module.
  380. \Drupal::service('config.manager')->uninstall('module', $module);
  381. // In order to make uninstalling transactional if anything uses routes.
  382. \Drupal::getContainer()->set('router.route_provider.old', \Drupal::service('router.route_provider'));
  383. \Drupal::getContainer()->set('router.route_provider', \Drupal::service('router.route_provider.lazy_builder'));
  384. // Notify interested components that this module's entity types are being
  385. // deleted. For example, a SQL-based storage handler can use this as an
  386. // opportunity to drop the corresponding database tables.
  387. // @todo Clean this up in https://www.drupal.org/node/2350111.
  388. $update_manager = \Drupal::entityDefinitionUpdateManager();
  389. /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
  390. $entity_field_manager = \Drupal::service('entity_field.manager');
  391. foreach ($entity_type_manager->getDefinitions() as $entity_type) {
  392. if ($entity_type->getProvider() == $module) {
  393. $update_manager->uninstallEntityType($entity_type);
  394. }
  395. elseif ($entity_type->entityClassImplements(FieldableEntityInterface::CLASS)) {
  396. // The module being uninstalled might have added new fields to
  397. // existing entity types. This will add them to the deleted fields
  398. // repository so their data will be purged on cron.
  399. foreach ($entity_field_manager->getFieldStorageDefinitions($entity_type->id()) as $storage_definition) {
  400. if ($storage_definition->getProvider() == $module) {
  401. $update_manager->uninstallFieldStorageDefinition($storage_definition);
  402. }
  403. }
  404. }
  405. }
  406. // Remove the schema.
  407. drupal_uninstall_schema($module);
  408. // Remove the module's entry from the config. Don't check schema when
  409. // uninstalling a module since we are only clearing a key.
  410. \Drupal::configFactory()->getEditable('core.extension')->clear("module.$module")->save(TRUE);
  411. // Update the module handler to remove the module.
  412. // The current ModuleHandler instance is obsolete with the kernel rebuild
  413. // below.
  414. $module_filenames = $this->moduleHandler->getModuleList();
  415. unset($module_filenames[$module]);
  416. $this->moduleHandler->setModuleList($module_filenames);
  417. // Remove any potential cache bins provided by the module.
  418. $this->removeCacheBins($module);
  419. // Clear the static cache of the "extension.list.module" service to pick
  420. // up the new module, since it merges the installation status of modules
  421. // into its statically cached list.
  422. \Drupal::service('extension.list.module')->reset();
  423. // Clear plugin manager caches.
  424. \Drupal::getContainer()->get('plugin.cache_clearer')->clearCachedDefinitions();
  425. // Update the kernel to exclude the uninstalled modules.
  426. $this->updateKernel($module_filenames);
  427. // Update the theme registry to remove the newly uninstalled module.
  428. drupal_theme_rebuild();
  429. // Modules can alter theme info, so refresh theme data.
  430. // @todo ThemeHandler cannot be injected into ModuleHandler, since that
  431. // causes a circular service dependency.
  432. // @see https://www.drupal.org/node/2208429
  433. \Drupal::service('theme_handler')->refreshInfo();
  434. \Drupal::logger('system')->info('%module module uninstalled.', ['%module' => $module]);
  435. $schema_store = \Drupal::keyValue('system.schema');
  436. $schema_store->delete($module);
  437. /** @var \Drupal\Core\Update\UpdateRegistry $post_update_registry */
  438. $post_update_registry = \Drupal::service('update.post_update_registry');
  439. $post_update_registry->filterOutInvokedUpdatesByModule($module);
  440. }
  441. // Rebuild routes after installing module. This is done here on top of
  442. // \Drupal\Core\Routing\RouteBuilder::destruct to not run into errors on
  443. // fastCGI which executes ::destruct() after the Module uninstallation page
  444. // was sent already.
  445. \Drupal::service('router.builder')->rebuild();
  446. drupal_get_installed_schema_version(NULL, TRUE);
  447. // Let other modules react.
  448. $this->moduleHandler->invokeAll('modules_uninstalled', [$module_list, $sync_status]);
  449. // Flush all persistent caches.
  450. // Any cache entry might implicitly depend on the uninstalled modules,
  451. // so clear all of them explicitly.
  452. $this->moduleHandler->invokeAll('cache_flush');
  453. foreach (Cache::getBins() as $service_id => $cache_backend) {
  454. $cache_backend->deleteAll();
  455. }
  456. return TRUE;
  457. }
  458. /**
  459. * Helper method for removing all cache bins registered by a given module.
  460. *
  461. * @param string $module
  462. * The name of the module for which to remove all registered cache bins.
  463. */
  464. protected function removeCacheBins($module) {
  465. $service_yaml_file = drupal_get_path('module', $module) . "/$module.services.yml";
  466. if (!file_exists($service_yaml_file)) {
  467. return;
  468. }
  469. $definitions = Yaml::decode(file_get_contents($service_yaml_file));
  470. $cache_bin_services = array_filter(
  471. isset($definitions['services']) ? $definitions['services'] : [],
  472. function ($definition) {
  473. $tags = isset($definition['tags']) ? $definition['tags'] : [];
  474. foreach ($tags as $tag) {
  475. if (isset($tag['name']) && ($tag['name'] == 'cache.bin')) {
  476. return TRUE;
  477. }
  478. }
  479. return FALSE;
  480. }
  481. );
  482. foreach (array_keys($cache_bin_services) as $service_id) {
  483. $backend = $this->kernel->getContainer()->get($service_id);
  484. if ($backend instanceof CacheBackendInterface) {
  485. $backend->removeBin();
  486. }
  487. }
  488. }
  489. /**
  490. * Updates the kernel module list.
  491. *
  492. * @param string $module_filenames
  493. * The list of installed modules.
  494. */
  495. protected function updateKernel($module_filenames) {
  496. // This reboots the kernel to register the module's bundle and its services
  497. // in the service container. The $module_filenames argument is taken over as
  498. // %container.modules% parameter, which is passed to a fresh ModuleHandler
  499. // instance upon first retrieval.
  500. $this->kernel->updateModules($module_filenames, $module_filenames);
  501. // After rebuilding the container we need to update the injected
  502. // dependencies.
  503. $container = $this->kernel->getContainer();
  504. $this->moduleHandler = $container->get('module_handler');
  505. }
  506. /**
  507. * {@inheritdoc}
  508. */
  509. public function validateUninstall(array $module_list) {
  510. $reasons = [];
  511. foreach ($module_list as $module) {
  512. foreach ($this->uninstallValidators as $validator) {
  513. $validation_reasons = $validator->validate($module);
  514. if (!empty($validation_reasons)) {
  515. if (!isset($reasons[$module])) {
  516. $reasons[$module] = [];
  517. }
  518. $reasons[$module] = array_merge($reasons[$module], $validation_reasons);
  519. }
  520. }
  521. }
  522. return $reasons;
  523. }
  524. }