diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index d0e748d..7e33986 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1636,9 +1636,14 @@ function install_profile_themes(&$install_state) { */ function install_install_profile(&$install_state) { \Drupal::service('module_installer')->install(array(drupal_get_profile()), FALSE); - // Install all available optional config. - // @todo discuss whether this is the desired approach? - \Drupal::service('config.installer')->installAllOptionalConfig(); + // Install all available optional config. During installation the module order + // is determined by dependencies. If there are no dependencies between modules + // then the order in which they are installed is dependent on random factors + // like PHP version. Optional configuration therefore might or might not be + // created depending on this order. Ensuring that we have installed all of the + // optional configuration whose dependencies can be met at this point removes + // any disparities that this creates. + \Drupal::service('config.installer')->installOptionalConfig(); } /** diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php index b5aeb7f..b77103e 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstaller.php +++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php @@ -89,7 +89,7 @@ public function __construct(ConfigFactoryInterface $config_factory, StorageInter * {@inheritdoc} */ public function installDefaultConfig($type, $name) { - $extension_path = drupal_get_path($type, $name); + $extension_path = $this->drupalGetPath($type, $name); // Refresh the schema cache if the extension provides configuration schema // or is a theme. if (is_dir($extension_path . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY) || $type == 'theme') { @@ -99,34 +99,37 @@ public function installDefaultConfig($type, $name) { $default_install_path = $this->getDefaultConfigDirectory($type, $name); if (is_dir($default_install_path)) { if (!$this->isSyncing()) { - // Set the source storage to the configuration provided by the extension. - $this->setSourceStorage(new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION)); - $prefix = NULL; + $storage = new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION); + $prefix = ''; } else { // The configuration importer sets the source storage on the config // installer. The configuration importer handles all of the // configuration entity imports. We only need to ensure that simple // configuration is created when the extension is installed. - $prefix = $name; + $storage = $this->getSourceStorage(); + $prefix = $name . '.'; } // Gather information about all the supported collections. $collection_info = $this->configManager->getConfigCollectionInfo(); - foreach ($collection_info->getCollectionNames(TRUE) as $collection) { - $config_to_install = $this->getDefaultConfigToInstall($collection, $prefix); - if (!empty($config_to_install)) { - $this->createConfiguration($collection, $config_to_install); + foreach ($collection_info->getCollectionNames() as $collection) { + $config_to_create = $this->getConfigToCreate($storage, $collection, $prefix); + if (!empty($config_to_create)) { + $this->createConfiguration($collection, $config_to_create); } } } - if (!$this->isSyncing() && !drupal_installation_attempted()) { + // During a drupal installation optional configuration is installed at the + // end of the installation process. + // @see install_install_profile() + if (!$this->isSyncing() && !$this->drupalInstallationAttempted()) { $optional_install_path = $extension_path . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY; if (is_dir($optional_install_path)) { // Install any optional config the module provides. - $this->setSourceStorage(new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION)); - $this->installOptionalConfig(); + $storage = new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION); + $this->installOptionalConfig($storage, ''); } // Install any optional configuration entities whose type this extension // provides. This searches all the installed modules config/optional @@ -136,92 +139,65 @@ public function installDefaultConfig($type, $name) { }, FALSE); if ($provides_config_entity_type) { $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE); - $this->setSourceStorage($storage); - $this->installOptionalConfig($name); + $this->installOptionalConfig($storage, $name . '.'); } } // Reset all the static caches and list caches. $this->configFactory->reset(); - $this->resetSourceStorage(); } /** * {@inheritdoc} */ - public function installAllOptionalConfig() { - $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, drupal_installation_attempted()); - $this->setSourceStorage($storage); - $this->installOptionalConfig(); - $this->resetSourceStorage(); - } - - /** - * Installs optional configuration. - * - * Optional configuration is only installed if: - * - the configuration does not exist already. - * - it's a configuration entity. - * - its dependencies can be met. - * - * @param string $name - * If set, limits the installed configuration to only configuration - * beginning with the provided value. - */ - protected function installOptionalConfig($name = NULL) { - if ($this->isSyncing()) { - return; + public function installOptionalConfig(StorageInterface $storage = NULL, $prefix = '') { + if (!$storage) { + $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, $this->drupalInstallationAttempted()); } $collection_info = $this->configManager->getConfigCollectionInfo(); $enabled_extensions = $this->getEnabledExtensions(); - $existing_config = $this->configFactory->listAll(); - foreach ($collection_info->getCollectionNames(TRUE) as $collection) { - $config_entity_support = $this->configManager->supportsConfigurationEntities($collection); - if (!$config_entity_support) { + $existing_config = $this->configFactory->listAll($prefix); + foreach ($collection_info->getCollectionNames() as $collection) { + if (!$this->configManager->supportsConfigurationEntities($collection)) { continue; } - $config_to_install = $this->getDefaultConfigToInstall($collection, $name); - $all_config = array_merge($existing_config, array_keys($config_to_install)); - foreach ($config_to_install as $config_name => $data) { - // Ensure that the configuration does not exist already, that it is a - // configuration entity, and that its dependencies can be met. + $config_to_create = $this->getConfigToCreate($storage, $collection, $prefix); + $all_config = array_merge($existing_config, array_keys($config_to_create)); + foreach ($config_to_create as $config_name => $data) { + // Exclude configuration that: + // - already exists + // - is a not configuration entity + // - or its dependencies cannot be met. if (in_array($config_name, $existing_config) || !$this->configManager->getEntityTypeIdByName($config_name) || !$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config)) { - unset($config_to_install[$config_name]); + unset($config_to_create[$config_name]); } } - if (!empty($config_to_install)) { - $this->createConfiguration($collection, $config_to_install, TRUE); + if (!empty($config_to_create)) { + $this->createConfiguration($collection, $config_to_create, TRUE); } } } /** - * Lists default configuration for an extension that is available to install. - * - * This looks in the extension's config/install directory and all of the - * currently enabled extensions config/install directories for configuration - * that begins with the extension's name. + * Gets configuration data from the provided storage to create. * + * @param StorageInterface $storage + * The configuration storage to read configuration from. * @param string $collection - * The configuration collection to install. - * @param string $name - * The name that configuration must start with. - - * - * @return array - * The list of configuration objects to create. + * The configuration collection to use. + * @param string $prefix + * (optional) Limit to configuration starting with the provided string. + * @return array An array of configuration data read from the source storage keyed by the + * An array of configuration data read from the source storage keyed by the */ - protected function getDefaultConfigToInstall($collection, $name = NULL) { - $prefix = ''; - if ($name) { - $prefix = $name . '.'; + protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '') { + if ($storage->getCollectionName() != $collection) { + $storage = $storage->createCollection($collection); } - $storage = $this->getSourceStorage($collection); - $list = $storage->listAll($prefix); - return $storage->readMultiple($list); + return $storage->readMultiple($storage->listAll($prefix)); } /** @@ -229,23 +205,23 @@ protected function getDefaultConfigToInstall($collection, $name = NULL) { * * @param string $collection * The configuration collection. - * @param array $data + * @param array $config_to_create * An array of configuration data to create, keyed by name. */ - protected function createConfiguration($collection, array $data) { + protected function createConfiguration($collection, array $config_to_create) { // Order the configuration to install in the order of dependencies. $config_entity_support = $this->configManager->supportsConfigurationEntities($collection); if ($config_entity_support) { $dependency_manager = new ConfigDependencyManager(); - $config_to_install = $dependency_manager - ->setData($data) + $config_names = $dependency_manager + ->setData($config_to_create) ->sortAll(); } else { - $config_to_install = array_keys($data); + $config_names = array_keys($config_to_create); } - foreach ($config_to_install as $name) { + foreach ($config_names as $name) { // Allow config factory overriders to use a custom configuration object if // they are responsible for the collection. $overrider = $this->configManager->getConfigCollectionInfo()->getOverrideService($collection); @@ -255,8 +231,8 @@ protected function createConfiguration($collection, array $data) { else { $new_config = new Config($name, $this->getActiveStorages($collection), $this->eventDispatcher, $this->typedConfig); } - if ($data[$name] !== FALSE) { - $new_config->setData($data[$name]); + if ($config_to_create[$name] !== FALSE) { + $new_config->setData($config_to_create[$name]); } if ($config_entity_support && $entity_type = $this->configManager->getEntityTypeIdByName($name)) { // If we are syncing do not create configuration entities. Pluggable @@ -296,7 +272,7 @@ protected function createConfiguration($collection, array $data) { * {@inheritdoc} */ public function installCollectionDefaultConfig($collection) { - $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, drupal_installation_attempted()); + $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, $this->drupalInstallationAttempted()); // Only install configuration for enabled extensions. $enabled_extensions = $this->getEnabledExtensions(); $config_to_install = array_filter($storage->listAll(), function ($config_name) use ($enabled_extensions) { @@ -319,35 +295,13 @@ public function setSourceStorage(StorageInterface $storage) { } /** - * Resets the configuration storage that provides the default configuration. - * - * @return $this - */ - protected function resetSourceStorage() { - // Do not reset the source storage when synchronizing configuration, since - // that would needlessly trigger a reload of the whole configuration to - // be imported. - if (!$this->isSyncing()) { - $this->sourceStorage = NULL; - } - return $this; - } - - /** * Gets the configuration storage that provides the default configuration. * - * @param string $collection - * (optional) The configuration collection. Defaults to the default - * collection. - * * @return \Drupal\Core\Config\StorageInterface|null * The configuration storage that provides the default configuration. * Returns null if the source storage has not been set. */ - public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION) { - if (isset($this->sourceStorage) && $this->sourceStorage->getCollectionName() != $collection) { - $this->sourceStorage = $this->sourceStorage->createCollection($collection); - } + public function getSourceStorage() { return $this->sourceStorage; } @@ -372,6 +326,9 @@ protected function getActiveStorages($collection = StorageInterface::DEFAULT_COL * {@inheritdoc} */ public function setSyncing($status) { + if (!$status) { + $this->sourceStorage = NULL; + } $this->isSyncing = $status; return $this; } @@ -396,17 +353,18 @@ public function isSyncing() { * reinstalled. * * @return array - * Array of configuration objects that already exist keyed by collection. + * Array of configuration object names that already exist keyed by + * collection. */ - protected function findPreExistingConfiguration() { + protected function findPreExistingConfiguration(StorageInterface $storage) { $existing_configuration = array(); // Gather information about all the supported collections. $collection_info = $this->configManager->getConfigCollectionInfo(); - foreach ($collection_info->getCollectionNames(TRUE) as $collection) { - $config_to_install = array_keys($this->getDefaultConfigToInstall($collection)); + foreach ($collection_info->getCollectionNames() as $collection) { + $config_to_create = array_keys($this->getConfigToCreate($storage, $collection)); $active_storage = $this->getActiveStorages($collection); - foreach ($config_to_install as $config_name) { + foreach ($config_to_create as $config_name) { if ($active_storage->exists($config_name)) { $existing_configuration[$collection][] = $config_name; } @@ -421,7 +379,7 @@ protected function findPreExistingConfiguration() { public function checkConfigurationToInstall($type, $name) { if ($this->isSyncing()) { // Configuration is assumed to already be checked by the config importer - // validation events + // validation events. return; } $config_install_path = $this->getDefaultConfigDirectory($type, $name); @@ -429,14 +387,14 @@ public function checkConfigurationToInstall($type, $name) { return; } - $this->setSourceStorage(new FileStorage($config_install_path, StorageInterface::DEFAULT_COLLECTION)); + $storage = new FileStorage($config_install_path, StorageInterface::DEFAULT_COLLECTION); $enabled_extensions = $this->getEnabledExtensions(); // Add the extension that will be enabled to the list of enabled extensions. $enabled_extensions[] = $name; // Check the dependencies of configuration provided by the module. - $invalid_default_config = $this->findDefaultConfigWithUnmetDependencies($enabled_extensions); + $invalid_default_config = $this->findDefaultConfigWithUnmetDependencies($storage, $enabled_extensions); if (!empty($invalid_default_config)) { throw UnmetDependenciesException::create($name, $invalid_default_config); } @@ -447,7 +405,7 @@ public function checkConfigurationToInstall($type, $name) { // Throw an exception if the module being installed contains configuration // that already exists. Additionally, can not continue installing more // modules because those may depend on the current module being installed. - $existing_configuration = $this->findPreExistingConfiguration(); + $existing_configuration = $this->findPreExistingConfiguration($storage); if (!empty($existing_configuration)) { throw PreExistingConfigException::create($name, $existing_configuration); } @@ -463,11 +421,11 @@ public function checkConfigurationToInstall($type, $name) { * @return array * List of configuration that has unmet dependencies */ - protected function findDefaultConfigWithUnmetDependencies(array $enabled_extensions) { - $config_data = $this->getDefaultConfigToInstall(StorageInterface::DEFAULT_COLLECTION); - $all_config = array_merge($this->configFactory->listAll(), array_keys($config_data)); - return array_filter(array_keys($config_data), function($config_name) use ($enabled_extensions, $all_config, $config_data) { - return !$this->validateDependencies($config_name, $config_data[$config_name], $enabled_extensions, $all_config); + protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions) { + $config_to_create = $this->getConfigToCreate($storage, StorageInterface::DEFAULT_COLLECTION); + $all_config = array_merge($this->configFactory->listAll(), array_keys($config_to_create)); + return array_filter(array_keys($config_to_create), function($config_name) use ($enabled_extensions, $all_config, $config_to_create) { + return !$this->validateDependencies($config_name, $config_to_create[$config_name], $enabled_extensions, $all_config); }); } @@ -499,6 +457,7 @@ protected function validateDependencies($config_name, array $data, array $enable // Ensure enforced dependencies are included. if (isset($all_dependencies['enforced'])) { $all_dependencies = array_merge($all_dependencies, $data['dependencies']['enforced']); + unset($all_dependencies['enforced']); } // Ensure the configuration entity type provider is in the list of // dependencies. @@ -594,4 +553,14 @@ protected function drupalGetProfile() { return drupal_get_profile(); } + /** + * Wrapper for drupal_installation_attempted(). + * + * @return bool + * TRUE if a Drupal installation is currently being attempted. + */ + protected function drupalInstallationAttempted() { + return drupal_installation_attempted(); + } + } diff --git a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php index bd2dad7..6d8e489 100644 --- a/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php +++ b/core/lib/Drupal/Core/Config/ConfigInstallerInterface.php @@ -38,14 +38,22 @@ public function installDefaultConfig($type, $name); /** - * Installs all optional configuration provided by any extension. + * Installs optional configuration. * * Optional configuration is only installed if: * - the configuration does not exist already. * - it's a configuration entity. * - its dependencies can be met. + * + * @param \Drupal\Core\Config\StorageInterface + * (optional) The configuration storage to search for optional + * configuration. If not provided, all enabled extension's optional + * configuration directories will be searched. + * @param string $prefix + * (optional) If set, limits the installed configuration to only + * configuration beginning with the provided value. */ - public function installAllOptionalConfig(); + public function installOptionalConfig(StorageInterface $storage = NULL, $prefix = ''); /** * Installs all default configuration in the specified collection. diff --git a/core/modules/config/src/Tests/ConfigInstallTest.php b/core/modules/config/src/Tests/ConfigInstallTest.php index 00be940..6ecd266 100644 --- a/core/modules/config/src/Tests/ConfigInstallTest.php +++ b/core/modules/config/src/Tests/ConfigInstallTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Config\StorageInterface; +use Drupal\Core\Config\UnmetDependenciesException; use Drupal\simpletest\KernelTestBase; /** @@ -48,8 +49,7 @@ function testModuleInstallation() { $this->assertFalse(\Drupal::service('config.typed')->hasConfigSchema('config_schema_test.schema_in_install'), 'Configuration schema for config_schema_test.schema_in_install does not exist.'); // Install the test module. - $this->enableModules(array('config_test')); - $this->installConfig(array('config_test')); + $this->installModules(array('config_test')); // Verify that default module config exists. \Drupal::configFactory()->reset($default_config); @@ -174,8 +174,7 @@ public function testCollectionInstallationCollectionConfigEntity() { ); \Drupal::state()->set('config_collection_install_test.collection_names', $collections); // Install the test module. - $this->enableModules(array('config_test', 'config_collection_install_test')); - $this->installConfig(array('config_test', 'config_collection_install_test')); + $this->installModules(array('config_test', 'config_collection_install_test')); /** @var \Drupal\Core\Config\StorageInterface $active_storage */ $active_storage = \Drupal::service('config.storage'); $this->assertEqual($collections, $active_storage->getAllCollectionNames()); @@ -193,4 +192,34 @@ public function testCollectionInstallationCollectionConfigEntity() { $this->assertIdentical(array('label' => 'entity'), $data); } + /** + * Tests the configuration with unmet dependencies is not installed. + */ + public function testDependencyChecking() { + $this->installModules(['config_test']); + try { + $this->installModules(['config_install_dependency_test']); + $this->fail('Expected UnmetDependenciesException not thrown.'); + } + catch (UnmetDependenciesException $e) { + $this->assertEqual($e->getExtension(), 'config_install_dependency_test'); + $this->assertEqual($e->getConfigObjects(), ['config_test.dynamic.other_module_test_with_dependency']); + $this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.other_module_test_with_dependency) provided by config_install_dependency_test have unmet dependencies'); + } + $this->installModules(['config_other_module_config_test']); + $this->installModules(['config_install_dependency_test']); + $this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.'); + } + + /** + * Installs a module. + * + * @param array $modules + * The module names. + */ + protected function installModules(array $modules) { + $this->container->get('module_installer')->install($modules); + $this->container = \Drupal::getContainer(); + } + } diff --git a/core/modules/config/src/Tests/ConfigOtherModuleTest.php b/core/modules/config/src/Tests/ConfigOtherModuleTest.php index 748d125..dea0e49 100644 --- a/core/modules/config/src/Tests/ConfigOtherModuleTest.php +++ b/core/modules/config/src/Tests/ConfigOtherModuleTest.php @@ -10,6 +10,7 @@ use Drupal\Core\Config\PreExistingConfigException; use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\UnmetDependenciesException; +use Drupal\simpletest\KernelTestBase; use Drupal\simpletest\WebTestBase; /** @@ -17,7 +18,7 @@ * * @group config */ -class ConfigOtherModuleTest extends WebTestBase { +class ConfigOtherModuleTest extends KernelTestBase { /** * Tests enabling the provider of the default configuration first. @@ -90,25 +91,6 @@ public function testUninstall() { } /** - * Tests the configuration with unmet dependencies is not installed. - */ - public function testDependencyChecking() { - try { - $this->installModule('config_install_dependency_test'); - $this->fail('Expected UnmetDependenciesException not thrown.'); - } - catch (UnmetDependenciesException $e) { - $this->assertEqual($e->getExtension(), 'config_install_dependency_test'); - $this->assertEqual($e->getConfigObjects(), ['config_test.dynamic.other_module_test_with_dependency']); - $this->assertEqual($e->getMessage(), 'Configuration objects (config_test.dynamic.other_module_test_with_dependency) provided by config_install_dependency_test have unmet dependencies'); - } - $this->installModule('config_test'); - $this->installModule('config_other_module_config_test'); - $this->installModule('config_install_dependency_test'); - $this->assertTrue(entity_load('config_test', 'other_module_test_with_dependency'), 'The config_test.dynamic.other_module_test_with_dependency configuration has been created during install.'); - } - - /** * Installs a module. * * @param string $module diff --git a/core/modules/language/config/install/tour.tour.language-add.yml b/core/modules/language/config/optional/tour.tour.language-add.yml similarity index 100% rename from core/modules/language/config/install/tour.tour.language-add.yml rename to core/modules/language/config/optional/tour.tour.language-add.yml diff --git a/core/modules/language/config/install/tour.tour.language-edit.yml b/core/modules/language/config/optional/tour.tour.language-edit.yml similarity index 100% rename from core/modules/language/config/install/tour.tour.language-edit.yml rename to core/modules/language/config/optional/tour.tour.language-edit.yml diff --git a/core/modules/language/config/install/tour.tour.language.yml b/core/modules/language/config/optional/tour.tour.language.yml similarity index 100% rename from core/modules/language/config/install/tour.tour.language.yml rename to core/modules/language/config/optional/tour.tour.language.yml diff --git a/core/modules/views/src/Tests/ViewExecutableTest.php b/core/modules/views/src/Tests/ViewExecutableTest.php index accd01f..30b470e 100644 --- a/core/modules/views/src/Tests/ViewExecutableTest.php +++ b/core/modules/views/src/Tests/ViewExecutableTest.php @@ -21,7 +21,6 @@ use Drupal\views\Plugin\views\query\Sql; use Drupal\views\Plugin\views\pager\PagerPluginBase; use Drupal\views\Plugin\views\query\QueryPluginBase; -use Drupal\views\ViewsDataHelper; use Drupal\views_test_data\Plugin\views\display\DisplayTest; use Symfony\Component\HttpFoundation\Response; @@ -86,9 +85,6 @@ protected function setUpFixtures() { $this->installEntitySchema('comment'); $this->installSchema('comment', array('comment_entity_statistics')); $this->installConfig(array('system', 'field', 'node', 'comment')); - // Clear the views data cache so it's updated with the new entity - // information. - \Drupal::service('views.views_data')->clear(); entity_create('node_type', array( 'type' => 'page',