ConfigConfigurator.php
Same filename in other branches
Namespace
Drupal\Core\RecipeFile
-
core/
lib/ Drupal/ Core/ Recipe/ ConfigConfigurator.php
View source
<?php
declare (strict_types=1);
namespace Drupal\Core\Recipe;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\NullStorage;
use Drupal\Core\Config\StorageInterface;
/**
* @internal
* This API is experimental.
*/
final class ConfigConfigurator {
public readonly ?string $recipeConfigDirectory;
private readonly bool|array $strict;
/**
* @param array $config
* Config options for a recipe.
* @param string $recipe_directory
* The path to the recipe.
* @param \Drupal\Core\Config\StorageInterface $active_configuration
* The active configuration storage.
*/
public function __construct(array $config, string $recipe_directory, StorageInterface $active_configuration) {
$this->recipeConfigDirectory = is_dir($recipe_directory . '/config') ? $recipe_directory . '/config' : NULL;
// @todo Consider defaulting this to FALSE in https://drupal.org/i/3478669.
$this->strict = $config['strict'] ?? TRUE;
$recipe_storage = $this->getConfigStorage();
if ($this->strict === TRUE) {
$strict_list = $recipe_storage->listAll();
}
else {
$strict_list = $this->strict ?: [];
}
// Everything in the strict list needs to be identical in the recipe and
// active storage.
foreach ($strict_list as $config_name) {
if ($active_data = $active_configuration->read($config_name)) {
// @todo https://www.drupal.org/i/3439714 Investigate if there is any
// generic code in core for this.
unset($active_data['uuid'], $active_data['_core']);
if (empty($active_data['dependencies'])) {
unset($active_data['dependencies']);
}
$recipe_data = $recipe_storage->read($config_name);
if (empty($recipe_data['dependencies'])) {
unset($recipe_data['dependencies']);
}
// Ensure we don't get a false mismatch due to differing key order.
// @todo When https://www.drupal.org/project/drupal/issues/3230826 is
// fixed in core, use that API instead to sort the config data.
self::recursiveSortByKey($active_data);
self::recursiveSortByKey($recipe_data);
if ($active_data !== $recipe_data) {
throw new RecipePreExistingConfigException($config_name, sprintf("The configuration '%s' exists already and does not match the recipe's configuration", $config_name));
}
}
}
}
/**
* Sorts an array recursively, by key, alphabetically.
*
* @param mixed[] $data
* The array to sort, passed by reference.
*
* @todo Remove when https://www.drupal.org/project/drupal/issues/3230826 is
* fixed in core.
*/
private static function recursiveSortByKey(array &$data) : void {
// If the array is a list, it is by definition already sorted.
if (!array_is_list($data)) {
ksort($data);
}
foreach ($data as &$value) {
if (is_array($value)) {
self::recursiveSortByKey($value);
}
}
}
/**
* Gets a config storage object for reading config from the recipe.
*
* @return \Drupal\Core\Config\StorageInterface
* The config storage object for reading config from the recipe.
*/
public function getConfigStorage() : StorageInterface {
$storages = [];
if ($this->recipeConfigDirectory) {
// Config provided by the recipe should take priority over config from
// extensions.
$storages[] = new FileStorage($this->recipeConfigDirectory);
}
if (!empty($this->config['import'])) {
/** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */
$module_list = \Drupal::service('extension.list.module');
/** @var \Drupal\Core\Extension\ThemeExtensionList $theme_list */
$theme_list = \Drupal::service('extension.list.theme');
foreach ($this->config['import'] as $extension => $names) {
// If the recipe explicitly does not want to import any config from this
// extension, skip it.
if ($names === NULL) {
continue;
}
$path = match (TRUE) { $module_list->exists($extension) => $module_list->getPath($extension),
$theme_list->exists($extension) => $theme_list->getPath($extension),
default => throw new \RuntimeException("{$extension} is not a theme or module"),
};
$storage = new RecipeConfigStorageWrapper(new FileStorage($path . '/config/install'), new FileStorage($path . '/config/optional'));
// If we get here, $names is either '*', or a list of config names
// provided by the current extension. In the latter case, we only want
// to import the config that is in the list, so use an
// AllowListConfigStorage to filter out the extension's other config.
if ($names && is_array($names)) {
$storage = new AllowListConfigStorage($storage, $names);
}
$storages[] = $storage;
}
}
$storage = RecipeConfigStorageWrapper::createStorageFromArray($storages);
if ($this->strict) {
return $storage;
}
// If we're not in strict mode, we only want to import config that doesn't
// exist yet in active storage.
$names = array_diff($storage->listAll(), \Drupal::service('config.storage')->listAll());
return $names ? new AllowListConfigStorage($storage, $names) : new NullStorage();
}
/**
* Determines if the recipe has any config or config actions to apply.
*
* @return bool
* TRUE if the recipe has any config or config actions to apply, FALSE if
* not.
*/
public function hasTasks() : bool {
return $this->recipeConfigDirectory !== NULL || count($this->config);
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
ConfigConfigurator | @internal This API is experimental. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.