Issue #2262861 by alexpott: Add concept of collections to config storages.
parent
809b361f20
commit
4e451f6566
|
@ -8,12 +8,12 @@
|
|||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Cache\CacheFactoryInterface;
|
||||
|
||||
/**
|
||||
* Defines the cached storage.
|
||||
*
|
||||
* The class gets another storage and a cache backend injected. It reads from
|
||||
* The class gets another storage and the cache factory injected. It reads from
|
||||
* the cache and delegates the read to the storage on a cache miss. It also
|
||||
* handles cache invalidation.
|
||||
*/
|
||||
|
@ -26,6 +26,13 @@ class CachedStorage implements StorageInterface, StorageCacheInterface {
|
|||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The cache factory.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheFactoryInterface
|
||||
*/
|
||||
protected $cacheFactory;
|
||||
|
||||
/**
|
||||
* The instantiated Cache backend.
|
||||
*
|
||||
|
@ -45,12 +52,20 @@ class CachedStorage implements StorageInterface, StorageCacheInterface {
|
|||
*
|
||||
* @param \Drupal\Core\Config\StorageInterface $storage
|
||||
* A configuration storage to be cached.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* A cache backend instance to use for caching.
|
||||
* @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory
|
||||
* A cache factory used for getting cache backends.
|
||||
*/
|
||||
public function __construct(StorageInterface $storage, CacheBackendInterface $cache) {
|
||||
public function __construct(StorageInterface $storage, CacheFactoryInterface $cache_factory) {
|
||||
$this->storage = $storage;
|
||||
$this->cache = $cache;
|
||||
$this->cacheFactory = $cache_factory;
|
||||
$collection = $this->getCollectionName();
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$bin = 'config';
|
||||
}
|
||||
else {
|
||||
$bin = 'config_' . str_replace('.', '_', $collection);
|
||||
}
|
||||
$this->cache = $this->cacheFactory->get($bin);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -238,4 +253,29 @@ class CachedStorage implements StorageInterface, StorageCacheInterface {
|
|||
public function resetListCache() {
|
||||
$this->findByPrefixCache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
return new static(
|
||||
$this->storage->createCollection($collection),
|
||||
$this->cacheFactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames() {
|
||||
return $this->storage->getAllCollectionNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollectionName() {
|
||||
return $this->storage->getCollectionName();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\ConfigCollectionNamesEvent.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps a configuration event for event listeners.
|
||||
*/
|
||||
class ConfigCollectionNamesEvent extends Event {
|
||||
|
||||
/**
|
||||
* Configuration collection names.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $collections = array();
|
||||
|
||||
/**
|
||||
* Adds names to the list of possible collections.
|
||||
*
|
||||
* @param array $collections
|
||||
* Collection names to add.
|
||||
*/
|
||||
public function addCollectionNames(array $collections) {
|
||||
$this->collections = array_merge($this->collections, $collections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a name to the list of possible collections.
|
||||
*
|
||||
* @param string $collection
|
||||
* Collection name to add.
|
||||
*/
|
||||
public function addCollectionName($collection) {
|
||||
$this->addCollectionNames(array($collection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of possible collection names.
|
||||
*
|
||||
* @return array
|
||||
* The list of possible collection names.
|
||||
*/
|
||||
public function getCollectionNames($include_default = TRUE) {
|
||||
sort($this->collections);
|
||||
$collections = array_unique($this->collections);
|
||||
if ($include_default) {
|
||||
array_unshift($collections, StorageInterface::DEFAULT_COLLECTION);
|
||||
}
|
||||
return $collections;
|
||||
}
|
||||
|
||||
}
|
|
@ -50,4 +50,11 @@ final class ConfigEvents {
|
|||
*/
|
||||
const IMPORT = 'config.importer.import';
|
||||
|
||||
/**
|
||||
* Name of event fired to discover all the possible configuration collections.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigInstaller::installDefaultConfig()
|
||||
*/
|
||||
const COLLECTION_NAMES = 'config.collection_names';
|
||||
|
||||
}
|
||||
|
|
|
@ -185,7 +185,9 @@ class ConfigImporter extends DependencySerialization {
|
|||
$this->moduleHandler = $module_handler;
|
||||
$this->themeHandler = $theme_handler;
|
||||
$this->stringTranslation = $string_translation;
|
||||
$this->processedConfiguration = $this->storageComparer->getEmptyChangelist();
|
||||
foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
|
||||
$this->processedConfiguration[$collection] = $this->storageComparer->getEmptyChangelist();
|
||||
}
|
||||
$this->processedExtensions = $this->getEmptyExtensionsProcessedList();
|
||||
}
|
||||
|
||||
|
@ -227,7 +229,9 @@ class ConfigImporter extends DependencySerialization {
|
|||
*/
|
||||
public function reset() {
|
||||
$this->storageComparer->reset();
|
||||
$this->processedConfiguration = $this->storageComparer->getEmptyChangelist();
|
||||
foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
|
||||
$this->processedConfiguration[$collection] = $this->storageComparer->getEmptyChangelist();
|
||||
}
|
||||
$this->processedExtensions = $this->getEmptyExtensionsProcessedList();
|
||||
$this->createExtensionChangelist();
|
||||
$this->validated = FALSE;
|
||||
|
@ -257,16 +261,13 @@ class ConfigImporter extends DependencySerialization {
|
|||
/**
|
||||
* Checks if there are any unprocessed configuration changes.
|
||||
*
|
||||
* @param array $ops
|
||||
* The operations to check for changes. Defaults to all operations, i.e.
|
||||
* array('delete', 'create', 'update', 'rename').
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there are changes to process and FALSE if not.
|
||||
*/
|
||||
public function hasUnprocessedConfigurationChanges($ops = array('delete', 'create', 'rename', 'update')) {
|
||||
foreach ($ops as $op) {
|
||||
if (count($this->getUnprocessedConfiguration($op))) {
|
||||
public function hasUnprocessedConfigurationChanges() {
|
||||
foreach ($this->storageComparer->getAllCollectionNames() as $collection)
|
||||
foreach (array('delete', 'create', 'rename', 'update') as $op) {
|
||||
if (count($this->getUnprocessedConfiguration($op, $collection))) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
@ -276,23 +277,29 @@ class ConfigImporter extends DependencySerialization {
|
|||
/**
|
||||
* Gets list of processed changes.
|
||||
*
|
||||
* @param string $collection
|
||||
* (optional) The configuration collection to get processed changes for.
|
||||
* Defaults to the default collection.
|
||||
*
|
||||
* @return array
|
||||
* An array containing a list of processed changes.
|
||||
*/
|
||||
public function getProcessedConfiguration() {
|
||||
return $this->processedConfiguration;
|
||||
public function getProcessedConfiguration($collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
return $this->processedConfiguration[$collection];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a change as processed.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection to set a change as processed for.
|
||||
* @param string $op
|
||||
* The change operation performed, either delete, create, rename, or update.
|
||||
* @param string $name
|
||||
* The name of the configuration processed.
|
||||
*/
|
||||
protected function setProcessedConfiguration($op, $name) {
|
||||
$this->processedConfiguration[$op][] = $name;
|
||||
protected function setProcessedConfiguration($collection, $op, $name) {
|
||||
$this->processedConfiguration[$collection][$op][] = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -301,12 +308,15 @@ class ConfigImporter extends DependencySerialization {
|
|||
* @param string $op
|
||||
* The change operation to get the unprocessed list for, either delete,
|
||||
* create, rename, or update.
|
||||
* @param string $collection
|
||||
* (optional) The configuration collection to get unprocessed changes for.
|
||||
* Defaults to the default collection.
|
||||
*
|
||||
* @return array
|
||||
* An array of configuration names.
|
||||
*/
|
||||
public function getUnprocessedConfiguration($op) {
|
||||
return array_diff($this->storageComparer->getChangelist($op), $this->processedConfiguration[$op]);
|
||||
public function getUnprocessedConfiguration($op, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
return array_diff($this->storageComparer->getChangelist($op, $collection), $this->processedConfiguration[$collection][$op]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -582,19 +592,29 @@ class ConfigImporter extends DependencySerialization {
|
|||
// into account.
|
||||
if ($this->totalConfigurationToProcess == 0) {
|
||||
$this->storageComparer->reset();
|
||||
foreach (array('delete', 'create', 'rename', 'update') as $op) {
|
||||
foreach ($this->getUnprocessedConfiguration($op) as $name) {
|
||||
$this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op));
|
||||
foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
|
||||
foreach (array('delete', 'create', 'rename', 'update') as $op) {
|
||||
$this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op, $collection));
|
||||
}
|
||||
}
|
||||
}
|
||||
$operation = $this->getNextConfigurationOperation();
|
||||
if (!empty($operation)) {
|
||||
if ($this->checkOp($operation['op'], $operation['name'])) {
|
||||
$this->processConfiguration($operation['op'], $operation['name']);
|
||||
if ($this->checkOp($operation['collection'], $operation['op'], $operation['name'])) {
|
||||
$this->processConfiguration($operation['collection'], $operation['op'], $operation['name']);
|
||||
}
|
||||
if ($operation['collection'] == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$context['message'] = $this->t('Synchronizing configuration: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
|
||||
}
|
||||
else {
|
||||
$context['message'] = $this->t('Synchronizing configuration: @op @name in @collection.', array('@op' => $operation['op'], '@name' => $operation['name'], '@collection' => $operation['collection']));
|
||||
}
|
||||
$processed_count = 0;
|
||||
foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
|
||||
foreach (array('delete', 'create', 'rename', 'update') as $op) {
|
||||
$processed_count += count($this->processedConfiguration[$collection][$op]);
|
||||
}
|
||||
}
|
||||
$context['message'] = t('Synchronizing configuration: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name']));
|
||||
$processed_count = count($this->processedConfiguration['create']) + count($this->processedConfiguration['delete']) + count($this->processedConfiguration['update']);
|
||||
$context['finished'] = $processed_count / $this->totalConfigurationToProcess;
|
||||
}
|
||||
else {
|
||||
|
@ -658,13 +678,16 @@ class ConfigImporter extends DependencySerialization {
|
|||
protected function getNextConfigurationOperation() {
|
||||
// The order configuration operations is processed is important. Deletes
|
||||
// have to come first so that recreates can work.
|
||||
foreach (array('delete', 'create', 'rename', 'update') as $op) {
|
||||
$config_names = $this->getUnprocessedConfiguration($op);
|
||||
if (!empty($config_names)) {
|
||||
return array(
|
||||
'op' => $op,
|
||||
'name' => array_shift($config_names),
|
||||
);
|
||||
foreach ($this->storageComparer->getAllCollectionNames() as $collection) {
|
||||
foreach (array('delete', 'create', 'rename', 'update') as $op) {
|
||||
$config_names = $this->getUnprocessedConfiguration($op, $collection);
|
||||
if (!empty($config_names)) {
|
||||
return array(
|
||||
'op' => $op,
|
||||
'name' => array_shift($config_names),
|
||||
'collection' => $collection,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
|
@ -708,6 +731,8 @@ class ConfigImporter extends DependencySerialization {
|
|||
/**
|
||||
* Processes a configuration change.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection to process changes for.
|
||||
* @param string $op
|
||||
* The change operation.
|
||||
* @param string $name
|
||||
|
@ -718,17 +743,21 @@ class ConfigImporter extends DependencySerialization {
|
|||
* set, otherwise the exception message is logged and the configuration
|
||||
* is skipped.
|
||||
*/
|
||||
protected function processConfiguration($op, $name) {
|
||||
protected function processConfiguration($collection, $op, $name) {
|
||||
try {
|
||||
if (!$this->importInvokeOwner($op, $name)) {
|
||||
$this->importConfig($op, $name);
|
||||
$processed = FALSE;
|
||||
if ($this->configManager->supportsConfigurationEntities($collection)) {
|
||||
$processed = $this->importInvokeOwner($collection, $op, $name);
|
||||
}
|
||||
if (!$processed) {
|
||||
$this->importConfig($collection, $op, $name);
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->logError($this->t('Unexpected error during import with operation @op for @name: @message', array('@op' => $op, '@name' => $name, '@message' => $e->getMessage())));
|
||||
// Error for that operation was logged, mark it as processed so that
|
||||
// the import can continue.
|
||||
$this->setProcessedConfiguration($op, $name);
|
||||
$this->setProcessedConfiguration($collection, $op, $name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -765,7 +794,7 @@ class ConfigImporter extends DependencySerialization {
|
|||
// the default or admin theme is change this will be picked up whilst
|
||||
// processing configuration.
|
||||
if ($op == 'disable' && $this->processedSystemTheme === FALSE) {
|
||||
$this->importConfig('update', 'system.theme');
|
||||
$this->importConfig(StorageInterface::DEFAULT_COLLECTION, 'update', 'system.theme');
|
||||
$this->configManager->getConfigFactory()->reset('system.theme');
|
||||
$this->processedSystemTheme = TRUE;
|
||||
}
|
||||
|
@ -785,6 +814,8 @@ class ConfigImporter extends DependencySerialization {
|
|||
* This method checks that the operation is still valid before processing a
|
||||
* configuration change.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
* @param string $op
|
||||
* The change operation.
|
||||
* @param string $name
|
||||
|
@ -795,10 +826,10 @@ class ConfigImporter extends DependencySerialization {
|
|||
* @return bool
|
||||
* TRUE is to continue processing, FALSE otherwise.
|
||||
*/
|
||||
protected function checkOp($op, $name) {
|
||||
protected function checkOp($collection, $op, $name) {
|
||||
if ($op == 'rename') {
|
||||
$names = $this->storageComparer->extractRenameNames($name);
|
||||
$target_exists = $this->storageComparer->getTargetStorage()->exists($names['new_name']);
|
||||
$target_exists = $this->storageComparer->getTargetStorage($collection)->exists($names['new_name']);
|
||||
if ($target_exists) {
|
||||
// If the target exists, the rename has already occurred as the
|
||||
// result of a secondary configuration write. Change the operation
|
||||
|
@ -810,13 +841,13 @@ class ConfigImporter extends DependencySerialization {
|
|||
}
|
||||
return TRUE;
|
||||
}
|
||||
$target_exists = $this->storageComparer->getTargetStorage()->exists($name);
|
||||
$target_exists = $this->storageComparer->getTargetStorage($collection)->exists($name);
|
||||
switch ($op) {
|
||||
case 'delete':
|
||||
if (!$target_exists) {
|
||||
// The configuration has already been deleted. For example, a field
|
||||
// is automatically deleted if all the instances are.
|
||||
$this->setProcessedConfiguration($op, $name);
|
||||
$this->setProcessedConfiguration($collection, $op, $name);
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
|
@ -833,7 +864,7 @@ class ConfigImporter extends DependencySerialization {
|
|||
$this->logError($this->t('Deleted and replaced configuration entity "@name"', array('@name' => $name)));
|
||||
}
|
||||
else {
|
||||
$this->storageComparer->getTargetStorage()->delete($name);
|
||||
$this->storageComparer->getTargetStorage($collection)->delete($name);
|
||||
$this->logError($this->t('Deleted and replaced configuration "@name"', array('@name' => $name)));
|
||||
}
|
||||
return TRUE;
|
||||
|
@ -846,7 +877,7 @@ class ConfigImporter extends DependencySerialization {
|
|||
// Mark as processed so that the synchronisation continues. Once the
|
||||
// the current synchronisation is complete it will show up as a
|
||||
// create.
|
||||
$this->setProcessedConfiguration($op, $name);
|
||||
$this->setProcessedConfiguration($collection, $op, $name);
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
|
@ -857,22 +888,24 @@ class ConfigImporter extends DependencySerialization {
|
|||
/**
|
||||
* Writes a configuration change from the source to the target storage.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
* @param string $op
|
||||
* The change operation.
|
||||
* @param string $name
|
||||
* The name of the configuration to process.
|
||||
*/
|
||||
protected function importConfig($op, $name) {
|
||||
$config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
|
||||
protected function importConfig($collection, $op, $name) {
|
||||
$config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
|
||||
if ($op == 'delete') {
|
||||
$config->delete();
|
||||
}
|
||||
else {
|
||||
$data = $this->storageComparer->getSourceStorage()->read($name);
|
||||
$data = $this->storageComparer->getSourceStorage($collection)->read($name);
|
||||
$config->setData($data ? $data : array());
|
||||
$config->save();
|
||||
}
|
||||
$this->setProcessedConfiguration($op, $name);
|
||||
$this->setProcessedConfiguration($collection, $op, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -883,6 +916,8 @@ class ConfigImporter extends DependencySerialization {
|
|||
*
|
||||
* @todo Add support for other extension types; e.g., themes etc.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
* @param string $op
|
||||
* The change operation to get the unprocessed list for, either delete,
|
||||
* create, rename, or update.
|
||||
|
@ -897,21 +932,21 @@ class ConfigImporter extends DependencySerialization {
|
|||
* TRUE if the configuration was imported as a configuration entity. FALSE
|
||||
* otherwise.
|
||||
*/
|
||||
protected function importInvokeOwner($op, $name) {
|
||||
protected function importInvokeOwner($collection, $op, $name) {
|
||||
// Renames are handled separately.
|
||||
if ($op == 'rename') {
|
||||
return $this->importInvokeRename($name);
|
||||
return $this->importInvokeRename($collection, $name);
|
||||
}
|
||||
// Validate the configuration object name before importing it.
|
||||
// Config::validateName($name);
|
||||
if ($entity_type = $this->configManager->getEntityTypeIdByName($name)) {
|
||||
$old_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
|
||||
if ($old_data = $this->storageComparer->getTargetStorage()->read($name)) {
|
||||
$old_config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
|
||||
if ($old_data = $this->storageComparer->getTargetStorage($collection)->read($name)) {
|
||||
$old_config->initWithData($old_data);
|
||||
}
|
||||
|
||||
$data = $this->storageComparer->getSourceStorage()->read($name);
|
||||
$new_config = new Config($name, $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
|
||||
$data = $this->storageComparer->getSourceStorage($collection)->read($name);
|
||||
$new_config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
|
||||
if ($data !== FALSE) {
|
||||
$new_config->setData($data);
|
||||
}
|
||||
|
@ -924,7 +959,7 @@ class ConfigImporter extends DependencySerialization {
|
|||
throw new EntityStorageException(String::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type)));
|
||||
}
|
||||
$entity_storage->$method($name, $new_config, $old_config);
|
||||
$this->setProcessedConfiguration($op, $name);
|
||||
$this->setProcessedConfiguration($collection, $op, $name);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
@ -933,6 +968,8 @@ class ConfigImporter extends DependencySerialization {
|
|||
/**
|
||||
* Imports a configuration entity rename.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
* @param string $rename_name
|
||||
* The rename configuration name, as provided by
|
||||
* \Drupal\Core\Config\StorageComparer::createRenameName().
|
||||
|
@ -943,16 +980,16 @@ class ConfigImporter extends DependencySerialization {
|
|||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter::createRenameName()
|
||||
*/
|
||||
protected function importInvokeRename($rename_name) {
|
||||
protected function importInvokeRename($collection, $rename_name) {
|
||||
$names = $this->storageComparer->extractRenameNames($rename_name);
|
||||
$entity_type_id = $this->configManager->getEntityTypeIdByName($names['old_name']);
|
||||
$old_config = new Config($names['old_name'], $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
|
||||
if ($old_data = $this->storageComparer->getTargetStorage()->read($names['old_name'])) {
|
||||
$old_config = new Config($names['old_name'], $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
|
||||
if ($old_data = $this->storageComparer->getTargetStorage($collection)->read($names['old_name'])) {
|
||||
$old_config->initWithData($old_data);
|
||||
}
|
||||
|
||||
$data = $this->storageComparer->getSourceStorage()->read($names['new_name']);
|
||||
$new_config = new Config($names['new_name'], $this->storageComparer->getTargetStorage(), $this->eventDispatcher, $this->typedConfigManager);
|
||||
$data = $this->storageComparer->getSourceStorage($collection)->read($names['new_name']);
|
||||
$new_config = new Config($names['new_name'], $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager);
|
||||
if ($data !== FALSE) {
|
||||
$new_config->setData($data);
|
||||
}
|
||||
|
@ -964,7 +1001,7 @@ class ConfigImporter extends DependencySerialization {
|
|||
throw new EntityStorageException(String::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type_id)));
|
||||
}
|
||||
$entity_storage->importRename($names['old_name'], $new_config, $old_config);
|
||||
$this->setProcessedConfiguration('rename', $rename_name);
|
||||
$this->setProcessedConfiguration($collection, 'rename', $rename_name);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
|
@ -88,10 +88,6 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function installDefaultConfig($type, $name) {
|
||||
// Get all default configuration owned by this extension.
|
||||
$source_storage = $this->getSourceStorage();
|
||||
$config_to_install = $source_storage->listAll($name . '.');
|
||||
|
||||
$extension_path = drupal_get_path($type, $name);
|
||||
// If the extension provides configuration schema clear the definitions.
|
||||
if (is_dir($extension_path . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY)) {
|
||||
|
@ -100,22 +96,61 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
$this->typedConfig->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
// Gather all the supported collection names.
|
||||
$event = new ConfigCollectionNamesEvent();
|
||||
$this->eventDispatcher->dispatch(ConfigEvents::COLLECTION_NAMES, $event);
|
||||
$collections = $event->getCollectionNames();
|
||||
|
||||
$old_state = $this->configFactory->getOverrideState();
|
||||
$this->configFactory->setOverrideState(FALSE);
|
||||
|
||||
// Read enabled extensions directly from configuration to avoid circular
|
||||
// dependencies with ModuleHandler and ThemeHandler.
|
||||
$extension_config = $this->configFactory->get('core.extension');
|
||||
$enabled_extensions = array_keys((array) $extension_config->get('module'));
|
||||
$enabled_extensions += array_keys((array) $extension_config->get('theme'));
|
||||
|
||||
foreach ($collections as $collection) {
|
||||
$config_to_install = $this->listDefaultConfigCollection($collection, $type, $name, $enabled_extensions);
|
||||
if (!empty($config_to_install)) {
|
||||
$this->createConfiguration($collection, $config_to_install);
|
||||
}
|
||||
}
|
||||
$this->configFactory->setOverrideState($old_state);
|
||||
// Reset all the static caches and list caches.
|
||||
$this->configFactory->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs default configuration for a particular collection.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection to install.
|
||||
* @param string $type
|
||||
* The extension type; e.g., 'module' or 'theme'.
|
||||
* @param string $name
|
||||
* The name of the module or theme to install default configuration for.
|
||||
* @param array $enabled_extensions
|
||||
* A list of all the currently enabled modules and themes.
|
||||
*
|
||||
* @return array
|
||||
* The list of configuration objects to create.
|
||||
*/
|
||||
protected function listDefaultConfigCollection($collection, $type, $name, array $enabled_extensions) {
|
||||
// Get all default configuration owned by this extension.
|
||||
$source_storage = $this->getSourceStorage($collection);
|
||||
$config_to_install = $source_storage->listAll($name . '.');
|
||||
|
||||
// If not installing the core base system default configuration, work out if
|
||||
// this extension provides default configuration for any other enabled
|
||||
// extensions.
|
||||
$extension_path = drupal_get_path($type, $name);
|
||||
if ($type !== 'core' && is_dir($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY)) {
|
||||
$enabled_extensions = $other_module_config = array();
|
||||
$default_storage = new FileStorage($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY);
|
||||
$default_storage = new FileStorage($extension_path . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection);
|
||||
$other_module_config = array_filter($default_storage->listAll(), function ($value) use ($name) {
|
||||
return !preg_match('/^' . $name . '\./', $value);
|
||||
});
|
||||
|
||||
// Read enabled extensions directly from configuration to avoid circular
|
||||
// dependencies with ModuleHandler and ThemeHandler.
|
||||
$extension_config = $this->configFactory->get('core.extension');
|
||||
$enabled_extensions += array_keys((array) $extension_config->get('module'));
|
||||
$enabled_extensions += array_keys((array) $extension_config->get('theme'));
|
||||
|
||||
$other_module_config = array_filter($other_module_config, function ($config_name) use ($enabled_extensions) {
|
||||
$provider = Unicode::substr($config_name, 0, strpos($config_name, '.'));
|
||||
return in_array($provider, $enabled_extensions);
|
||||
|
@ -124,63 +159,92 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
$config_to_install = array_merge($config_to_install, $other_module_config);
|
||||
}
|
||||
|
||||
if (!empty($config_to_install)) {
|
||||
// Order the configuration to install in the order of dependencies.
|
||||
$data = $source_storage->readMultiple($config_to_install);
|
||||
return $config_to_install;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates configuration in a collection based on the provided list.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
* @param array $config_to_install
|
||||
* A list of configuration object names to create.
|
||||
*/
|
||||
protected function createConfiguration($collection, array $config_to_install) {
|
||||
// Order the configuration to install in the order of dependencies.
|
||||
$data = $this->getSourceStorage($collection)->readMultiple($config_to_install);
|
||||
$config_entity_support = $this->configManager->supportsConfigurationEntities($collection);
|
||||
if ($config_entity_support) {
|
||||
$dependency_manager = new ConfigDependencyManager();
|
||||
$sorted_config = $dependency_manager
|
||||
$config_to_install = $dependency_manager
|
||||
->setData($data)
|
||||
->sortAll();
|
||||
}
|
||||
|
||||
$old_state = $this->configFactory->getOverrideState();
|
||||
$this->configFactory->setOverrideState(FALSE);
|
||||
// Remove configuration that already exists in the active storage.
|
||||
$config_to_install = array_diff($config_to_install, $this->getActiveStorage($collection)->listAll());
|
||||
|
||||
// Remove configuration that already exists in the active storage.
|
||||
$sorted_config = array_diff($sorted_config, $this->activeStorage->listAll());
|
||||
foreach ($config_to_install as $name) {
|
||||
$new_config = new Config($name, $this->getActiveStorage($collection), $this->eventDispatcher, $this->typedConfig);
|
||||
if ($data[$name] !== FALSE) {
|
||||
$new_config->setData($data[$name]);
|
||||
}
|
||||
if ($config_entity_support && $entity_type = $this->configManager->getEntityTypeIdByName($name)) {
|
||||
|
||||
foreach ($sorted_config as $name) {
|
||||
$new_config = new Config($name, $this->activeStorage, $this->eventDispatcher, $this->typedConfig);
|
||||
if ($data[$name] !== FALSE) {
|
||||
$new_config->setData($data[$name]);
|
||||
// If we are syncing do not create configuration entities. Pluggable
|
||||
// configuration entities can have dependencies on modules that are
|
||||
// not yet enabled. This approach means that any code that expects
|
||||
// default configuration entities to exist will be unstable after the
|
||||
// module has been enabled and before the config entity has been
|
||||
// imported.
|
||||
if ($this->isSyncing) {
|
||||
continue;
|
||||
}
|
||||
if ($entity_type = $this->configManager->getEntityTypeIdByName($name)) {
|
||||
|
||||
// If we are syncing do not create configuration entities. Pluggable
|
||||
// configuration entities can have dependencies on modules that are
|
||||
// not yet enabled. This approach means that any code that expects
|
||||
// default configuration entities to exist will be unstable after the
|
||||
// module has been enabled and before the config entity has been
|
||||
// imported.
|
||||
if ($this->isSyncing) {
|
||||
continue;
|
||||
}
|
||||
$entity_storage = $this->configManager
|
||||
->getEntityManager()
|
||||
->getStorage($entity_type);
|
||||
// It is possible that secondary writes can occur during configuration
|
||||
// creation. Updates of such configuration are allowed.
|
||||
if ($this->activeStorage->exists($name)) {
|
||||
$id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
|
||||
$entity = $entity_storage->load($id);
|
||||
foreach ($new_config->get() as $property => $value) {
|
||||
$entity->set($property, $value);
|
||||
}
|
||||
$entity->save();
|
||||
}
|
||||
else {
|
||||
$entity_storage
|
||||
->create($new_config->get())
|
||||
->save();
|
||||
$entity_storage = $this->configManager
|
||||
->getEntityManager()
|
||||
->getStorage($entity_type);
|
||||
// It is possible that secondary writes can occur during configuration
|
||||
// creation. Updates of such configuration are allowed.
|
||||
if ($this->getActiveStorage($collection)->exists($name)) {
|
||||
$id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
|
||||
$entity = $entity_storage->load($id);
|
||||
foreach ($new_config->get() as $property => $value) {
|
||||
$entity->set($property, $value);
|
||||
}
|
||||
$entity->save();
|
||||
}
|
||||
else {
|
||||
$new_config->save();
|
||||
$entity_storage
|
||||
->create($new_config->get())
|
||||
->save();
|
||||
}
|
||||
}
|
||||
$this->configFactory->setOverrideState($old_state);
|
||||
else {
|
||||
$new_config->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function installCollectionDefaultConfig($collection) {
|
||||
$config_to_install = $this->getSourceStorage($collection)->listAll();
|
||||
$extension_config = $this->configFactory->get('core.extension');
|
||||
$enabled_extensions = array_keys((array) $extension_config->get('module'));
|
||||
$enabled_extensions += array_keys((array) $extension_config->get('theme'));
|
||||
$config_to_install = array_filter($config_to_install, function ($config_name) use ($enabled_extensions) {
|
||||
$provider = Unicode::substr($config_name, 0, strpos($config_name, '.'));
|
||||
return in_array($provider, $enabled_extensions);
|
||||
});
|
||||
if (!empty($config_to_install)) {
|
||||
$old_state = $this->configFactory->getOverrideState();
|
||||
$this->configFactory->setOverrideState(FALSE);
|
||||
$this->createConfiguration($collection, $config_to_install);
|
||||
$this->configFactory->setOverrideState($old_state);
|
||||
// Reset all the static caches and list caches.
|
||||
$this->configFactory->reset();
|
||||
}
|
||||
// Reset all the static caches and list caches.
|
||||
$this->configFactory->reset();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,18 +266,42 @@ class ConfigInstaller implements ConfigInstallerInterface {
|
|||
/**
|
||||
* 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
|
||||
* The configuration storage that provides the default configuration.
|
||||
*/
|
||||
public function getSourceStorage() {
|
||||
public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if (!isset($this->sourceStorage)) {
|
||||
// Default to using the ExtensionInstallStorage which searches extension's
|
||||
// config directories for default configuration.
|
||||
$this->sourceStorage = new ExtensionInstallStorage($this->activeStorage);
|
||||
$this->sourceStorage = new ExtensionInstallStorage($this->activeStorage, InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection);
|
||||
}
|
||||
if ($this->sourceStorage->getCollectionName() != $collection) {
|
||||
$this->sourceStorage = $this->sourceStorage->createCollection($collection);
|
||||
}
|
||||
return $this->sourceStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration storage that provides the active configuration.
|
||||
*
|
||||
* @param string $collection
|
||||
* (optional) The configuration collection. Defaults to the default
|
||||
* collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* The configuration storage that provides the default configuration.
|
||||
*/
|
||||
protected function getActiveStorage($collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if ($this->activeStorage->getCollectionName() != $collection) {
|
||||
$this->activeStorage = $this->activeStorage->createCollection($collection);
|
||||
}
|
||||
return $this->activeStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -37,6 +37,19 @@ interface ConfigInstallerInterface {
|
|||
*/
|
||||
public function installDefaultConfig($type, $name);
|
||||
|
||||
/**
|
||||
* Installs all default configuration in the specified collection.
|
||||
*
|
||||
* The function is useful if the site needs to respond to an event that has
|
||||
* just created another collection and we need to check all the installed
|
||||
* extensions for any matching configuration. For example, if a language has
|
||||
* just been created.
|
||||
*
|
||||
* @param string $collection
|
||||
* The configuration collection.
|
||||
*/
|
||||
public function installCollectionDefaultConfig($collection);
|
||||
|
||||
/**
|
||||
* Sets the configuration storage that provides the default configuration.
|
||||
*
|
||||
|
|
|
@ -100,7 +100,11 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL) {
|
||||
public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if ($collection != StorageInterface::DEFAULT_COLLECTION) {
|
||||
$source_storage = $source_storage->createCollection($collection);
|
||||
$target_storage = $target_storage->createCollection($collection);
|
||||
}
|
||||
if (!isset($target_name)) {
|
||||
$target_name = $source_name;
|
||||
}
|
||||
|
@ -131,10 +135,23 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function createSnapshot(StorageInterface $source_storage, StorageInterface $snapshot_storage) {
|
||||
// Empty the snapshot of all configuration.
|
||||
$snapshot_storage->deleteAll();
|
||||
foreach ($snapshot_storage->getAllCollectionNames() as $collection) {
|
||||
$snapshot_collection = $snapshot_storage->createCollection($collection);
|
||||
$snapshot_collection->deleteAll();
|
||||
}
|
||||
foreach ($source_storage->listAll() as $name) {
|
||||
$snapshot_storage->write($name, $source_storage->read($name));
|
||||
}
|
||||
// Copy collections as well.
|
||||
foreach ($source_storage->getAllCollectionNames() as $collection) {
|
||||
$source_collection = $source_storage->createCollection($collection);
|
||||
$snapshot_collection = $snapshot_storage->createCollection($collection);
|
||||
foreach ($source_collection->listAll() as $name) {
|
||||
$snapshot_collection->write($name, $source_collection->read($name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,6 +173,13 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
foreach ($config_names as $config_name) {
|
||||
$this->configFactory->get($config_name)->delete();
|
||||
}
|
||||
|
||||
// Remove any matching configuration from collections.
|
||||
foreach ($this->activeStorage->getAllCollectionNames() as $collection) {
|
||||
$collection_storage = $this->activeStorage->createCollection($collection);
|
||||
$collection_storage->deleteAll($name . '.');
|
||||
}
|
||||
|
||||
$schema_dir = drupal_get_path($type, $name) . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY;
|
||||
if (is_dir($schema_dir)) {
|
||||
// Refresh the schema cache if uninstalling an extension that provides
|
||||
|
@ -216,4 +240,10 @@ class ConfigManager implements ConfigManagerInterface {
|
|||
return $entities_to_return;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsConfigurationEntities($collection) {
|
||||
return $collection == StorageInterface::DEFAULT_COLLECTION;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,13 +51,16 @@ interface ConfigManagerInterface {
|
|||
* @param string $target_name
|
||||
* (optional) The name of the configuration object in the target storage.
|
||||
* If omitted, the source name is used.
|
||||
* @param string $collection
|
||||
* (optional) The configuration collection name. Defaults to the default
|
||||
* collection.
|
||||
*
|
||||
* @return core/lib/Drupal/Component/Diff
|
||||
* A formatted string showing the difference between the two storages.
|
||||
*
|
||||
* @todo Make renderer injectable
|
||||
*/
|
||||
public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL);
|
||||
public function diff(StorageInterface $source_storage, StorageInterface $target_storage, $source_name, $target_name = NULL, $collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Creates a configuration snapshot following a successful import.
|
||||
|
@ -109,5 +112,15 @@ interface ConfigManagerInterface {
|
|||
*/
|
||||
public function findConfigEntityDependentsAsEntities($type, array $names);
|
||||
|
||||
/**
|
||||
* Determines if the provided collection supports configuration entities.
|
||||
*
|
||||
* @param string $collection
|
||||
* The collection to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the collection support configuration entities, FALSE if not.
|
||||
*/
|
||||
public function supportsConfigurationEntities($collection);
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,13 @@ class DatabaseStorage implements StorageInterface {
|
|||
*/
|
||||
protected $options = array();
|
||||
|
||||
/**
|
||||
* The storage collection.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $collection = StorageInterface::DEFAULT_COLLECTION;
|
||||
|
||||
/**
|
||||
* Constructs a new DatabaseStorage.
|
||||
*
|
||||
|
@ -46,11 +53,15 @@ class DatabaseStorage implements StorageInterface {
|
|||
* A database table name to store configuration data in.
|
||||
* @param array $options
|
||||
* (optional) Any additional database connection options to use in queries.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct(Connection $connection, $table, array $options = array()) {
|
||||
public function __construct(Connection $connection, $table, array $options = array(), $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->connection = $connection;
|
||||
$this->table = $table;
|
||||
$this->options = $options;
|
||||
$this->collection = $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,7 +69,8 @@ class DatabaseStorage implements StorageInterface {
|
|||
*/
|
||||
public function exists($name) {
|
||||
try {
|
||||
return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', 0, 1, array(
|
||||
return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name = :name', 0, 1, array(
|
||||
':collection' => $this->collection,
|
||||
':name' => $name,
|
||||
), $this->options)->fetchField();
|
||||
}
|
||||
|
@ -75,7 +87,7 @@ class DatabaseStorage implements StorageInterface {
|
|||
public function read($name) {
|
||||
$data = FALSE;
|
||||
try {
|
||||
$raw = $this->connection->query('SELECT data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', array(':name' => $name), $this->options)->fetchField();
|
||||
$raw = $this->connection->query('SELECT data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name = :name', array(':collection' => $this->collection, ':name' => $name), $this->options)->fetchField();
|
||||
if ($raw !== FALSE) {
|
||||
$data = $this->decode($raw);
|
||||
}
|
||||
|
@ -93,7 +105,7 @@ class DatabaseStorage implements StorageInterface {
|
|||
public function readMultiple(array $names) {
|
||||
$list = array();
|
||||
try {
|
||||
$list = $this->connection->query('SELECT name, data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name IN (:names)', array(':names' => $names), $this->options)->fetchAllKeyed();
|
||||
$list = $this->connection->query('SELECT name, data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name IN (:names)', array(':collection' => $this->collection, ':names' => $names), $this->options)->fetchAllKeyed();
|
||||
foreach ($list as &$data) {
|
||||
$data = $this->decode($data);
|
||||
}
|
||||
|
@ -136,7 +148,7 @@ class DatabaseStorage implements StorageInterface {
|
|||
protected function doWrite($name, $data) {
|
||||
$options = array('return' => Database::RETURN_AFFECTED) + $this->options;
|
||||
return (bool) $this->connection->merge($this->table, $options)
|
||||
->key('name', $name)
|
||||
->keys(array('collection', 'name'), array($this->collection, $name))
|
||||
->fields(array('data' => $data))
|
||||
->execute();
|
||||
}
|
||||
|
@ -176,8 +188,15 @@ class DatabaseStorage implements StorageInterface {
|
|||
$schema = array(
|
||||
'description' => 'The base table for configuration data.',
|
||||
'fields' => array(
|
||||
'collection' => array(
|
||||
'description' => 'Primary Key: Config object collection.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'name' => array(
|
||||
'description' => 'Primary Key: Unique config object name.',
|
||||
'description' => 'Primary Key: Config object name.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
|
@ -190,7 +209,7 @@ class DatabaseStorage implements StorageInterface {
|
|||
'size' => 'big',
|
||||
),
|
||||
),
|
||||
'primary key' => array('name'),
|
||||
'primary key' => array('collection', 'name'),
|
||||
);
|
||||
return $schema;
|
||||
}
|
||||
|
@ -205,6 +224,7 @@ class DatabaseStorage implements StorageInterface {
|
|||
public function delete($name) {
|
||||
$options = array('return' => Database::RETURN_AFFECTED) + $this->options;
|
||||
return (bool) $this->connection->delete($this->table, $options)
|
||||
->condition('collection', $this->collection)
|
||||
->condition('name', $name)
|
||||
->execute();
|
||||
}
|
||||
|
@ -220,6 +240,7 @@ class DatabaseStorage implements StorageInterface {
|
|||
return (bool) $this->connection->update($this->table, $options)
|
||||
->fields(array('name' => $new_name))
|
||||
->condition('name', $name)
|
||||
->condition('collection', $this->collection)
|
||||
->execute();
|
||||
}
|
||||
|
||||
|
@ -246,7 +267,8 @@ class DatabaseStorage implements StorageInterface {
|
|||
*/
|
||||
public function listAll($prefix = '') {
|
||||
try {
|
||||
return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name LIKE :name', array(
|
||||
return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name LIKE :name', array(
|
||||
':collection' => $this->collection,
|
||||
':name' => $this->connection->escapeLike($prefix) . '%',
|
||||
), $this->options)->fetchCol();
|
||||
}
|
||||
|
@ -263,10 +285,46 @@ class DatabaseStorage implements StorageInterface {
|
|||
$options = array('return' => Database::RETURN_AFFECTED) + $this->options;
|
||||
return (bool) $this->connection->delete($this->table, $options)
|
||||
->condition('name', $prefix . '%', 'LIKE')
|
||||
->condition('collection', $this->collection)
|
||||
->execute();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
return new static(
|
||||
$this->connection,
|
||||
$this->table,
|
||||
$this->options,
|
||||
$collection
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollectionName() {
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames() {
|
||||
try {
|
||||
return $this->connection->query('SELECT DISTINCT collection FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection <> :collection ORDER by collection', array(
|
||||
':collection' => StorageInterface::DEFAULT_COLLECTION)
|
||||
)->fetchCol();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -30,11 +30,26 @@ class ExtensionInstallStorage extends InstallStorage {
|
|||
* themes is stored.
|
||||
* @param string $directory
|
||||
* The directory to scan in each extension to scan for files. Defaults to
|
||||
* 'config'.
|
||||
* 'config/install'.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY) {
|
||||
public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->configStorage = $config_storage;
|
||||
$this->directory = $directory;
|
||||
$this->collection = $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
return new static(
|
||||
$this->configStorage,
|
||||
$this->directory,
|
||||
$collection
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,13 @@ use Drupal\Component\Utility\String;
|
|||
*/
|
||||
class FileStorage implements StorageInterface {
|
||||
|
||||
/**
|
||||
* The storage collection.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $collection;
|
||||
|
||||
/**
|
||||
* The filesystem path for configuration objects.
|
||||
*
|
||||
|
@ -28,9 +35,13 @@ class FileStorage implements StorageInterface {
|
|||
*
|
||||
* @param string $directory
|
||||
* A directory path to use for reading and writing of configuration files.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct($directory) {
|
||||
public function __construct($directory, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->directory = $directory;
|
||||
$this->collection = $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,7 +51,7 @@ class FileStorage implements StorageInterface {
|
|||
* The path to the configuration file.
|
||||
*/
|
||||
public function getFilePath($name) {
|
||||
return $this->directory . '/' . $name . '.' . static::getFileExtension();
|
||||
return $this->getCollectionDirectory() . '/' . $name . '.' . static::getFileExtension();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,10 +68,14 @@ class FileStorage implements StorageInterface {
|
|||
* Check if the directory exists and create it if not.
|
||||
*/
|
||||
protected function ensureStorage() {
|
||||
$success = file_prepare_directory($this->directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
|
||||
$success = $success && file_save_htaccess($this->directory, TRUE, TRUE);
|
||||
$dir = $this->getCollectionDirectory();
|
||||
$success = file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
|
||||
// Only create .htaccess file in root directory.
|
||||
if ($dir == $this->directory) {
|
||||
$success = $success && file_save_htaccess($this->directory, TRUE, TRUE);
|
||||
}
|
||||
if (!$success) {
|
||||
throw new StorageException("Failed to create config directory {$this->directory}");
|
||||
throw new StorageException('Failed to create config directory ' . $dir);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
@ -142,8 +157,9 @@ class FileStorage implements StorageInterface {
|
|||
*/
|
||||
public function delete($name) {
|
||||
if (!$this->exists($name)) {
|
||||
if (!file_exists($this->directory)) {
|
||||
throw new StorageException($this->directory . '/ not found.');
|
||||
$dir = $this->getCollectionDirectory();
|
||||
if (!file_exists($dir)) {
|
||||
throw new StorageException($dir . '/ not found.');
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -186,12 +202,13 @@ class FileStorage implements StorageInterface {
|
|||
public function listAll($prefix = '') {
|
||||
// glob() silently ignores the error of a non-existing search directory,
|
||||
// even with the GLOB_ERR flag.
|
||||
if (!file_exists($this->directory)) {
|
||||
$dir = $this->getCollectionDirectory();
|
||||
if (!file_exists($dir)) {
|
||||
return array();
|
||||
}
|
||||
$extension = '.' . static::getFileExtension();
|
||||
// \GlobIterator on Windows requires an absolute path.
|
||||
$files = new \GlobIterator(realpath($this->directory) . '/' . $prefix . '*' . $extension);
|
||||
$files = new \GlobIterator(realpath($dir) . '/' . $prefix . '*' . $extension);
|
||||
|
||||
$names = array();
|
||||
foreach ($files as $file) {
|
||||
|
@ -212,7 +229,110 @@ class FileStorage implements StorageInterface {
|
|||
$success = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if ($success && $this->collection != StorageInterface::DEFAULT_COLLECTION) {
|
||||
// Remove empty directories.
|
||||
if (!(new \FilesystemIterator($this->getCollectionDirectory()))->valid()) {
|
||||
drupal_rmdir($this->getCollectionDirectory());
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
return new static(
|
||||
$this->directory,
|
||||
$collection
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollectionName() {
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames() {
|
||||
$collections = $this->getAllCollectionNamesHelper($this->directory);
|
||||
sort($collections);
|
||||
return $collections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for getAllCollectionNames().
|
||||
*
|
||||
* If the file storage has the following subdirectory structure:
|
||||
* ./another_collection/one
|
||||
* ./another_collection/two
|
||||
* ./collection/sub/one
|
||||
* ./collection/sub/two
|
||||
* this function will return:
|
||||
* @code
|
||||
* array(
|
||||
* 'another_collection.one',
|
||||
* 'another_collection.two',
|
||||
* 'collection.sub.one',
|
||||
* 'collection.sub.two',
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* @param string $directory
|
||||
* The directory to check for sub directories. This allows this
|
||||
* function to be used recursively to discover all the collections in the
|
||||
* storage.
|
||||
*
|
||||
* @return array
|
||||
* A list of collection names contained within the provided directory.
|
||||
*/
|
||||
protected function getAllCollectionNamesHelper($directory) {
|
||||
$collections = array();
|
||||
foreach (new \DirectoryIterator($directory) as $fileinfo) {
|
||||
if ($fileinfo->isDir() && !$fileinfo->isDot()) {
|
||||
$collection = $fileinfo->getFilename();
|
||||
// Recursively call getAllCollectionNamesHelper() to discover if there
|
||||
// are subdirectories. Subdirectories represent a dotted collection
|
||||
// name.
|
||||
$sub_collections = $this->getAllCollectionNamesHelper($directory . '/' . $collection);
|
||||
if (!empty($sub_collections)) {
|
||||
// Build up the collection name by concatenating the subdirectory
|
||||
// names with the current directory name.
|
||||
foreach ($sub_collections as $sub_collection) {
|
||||
$collections[] = $collection . '.' . $sub_collection;
|
||||
}
|
||||
}
|
||||
// Check that the collection is valid by searching if for configuration
|
||||
// objects. A directory without any configuration objects is not a valid
|
||||
// collection.
|
||||
// \GlobIterator on Windows requires an absolute path.
|
||||
$files = new \GlobIterator(realpath($directory . '/' . $collection) . '/*.' . $this->getFileExtension());
|
||||
if (count($files)) {
|
||||
$collections[] = $collection;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $collections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the directory for the collection.
|
||||
*
|
||||
* @return string
|
||||
* The directory for the collection.
|
||||
*/
|
||||
protected function getCollectionDirectory() {
|
||||
if ($this->collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$dir = $this->directory;
|
||||
}
|
||||
else {
|
||||
$dir = $this->directory . '/' . str_replace('.', '/', $this->collection);
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -51,10 +51,14 @@ class InstallStorage extends FileStorage {
|
|||
*
|
||||
* @param string $directory
|
||||
* The directory to scan in each extension to scan for files. Defaults to
|
||||
* 'config'.
|
||||
* 'config/install'.
|
||||
* @param string $collection
|
||||
* (optional) The collection to store configuration in. Defaults to the
|
||||
* default collection.
|
||||
*/
|
||||
public function __construct($directory = self::CONFIG_INSTALL_DIRECTORY) {
|
||||
public function __construct($directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->directory = $directory;
|
||||
$this->collection = $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,7 +202,7 @@ class InstallStorage extends FileStorage {
|
|||
* The configuration folder name for this component.
|
||||
*/
|
||||
protected function getComponentFolder($type, $name) {
|
||||
return drupal_get_path($type, $name) . '/' . $this->directory;
|
||||
return drupal_get_path($type, $name) . '/' . $this->getCollectionDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -92,4 +92,26 @@ class NullStorage implements StorageInterface {
|
|||
public function deleteAll($prefix = '') {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createCollection($collection) {
|
||||
// No op.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCollectionName() {
|
||||
return '';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,11 +9,12 @@ namespace Drupal\Core\Config;
|
|||
|
||||
use Drupal\Component\Utility\String;
|
||||
use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
||||
use Drupal\Core\DependencyInjection\DependencySerialization;
|
||||
|
||||
/**
|
||||
* Defines a config storage comparer.
|
||||
*/
|
||||
class StorageComparer implements StorageComparerInterface {
|
||||
class StorageComparer extends DependencySerialization implements StorageComparerInterface {
|
||||
|
||||
/**
|
||||
* The source storage used to discover configuration changes.
|
||||
|
@ -22,6 +23,13 @@ class StorageComparer implements StorageComparerInterface {
|
|||
*/
|
||||
protected $sourceStorage;
|
||||
|
||||
/**
|
||||
* The source storages keyed by collection.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface[]
|
||||
*/
|
||||
protected $sourceStorages;
|
||||
|
||||
/**
|
||||
* The target storage used to write configuration changes.
|
||||
*
|
||||
|
@ -29,9 +37,25 @@ class StorageComparer implements StorageComparerInterface {
|
|||
*/
|
||||
protected $targetStorage;
|
||||
|
||||
/**
|
||||
* The target storages keyed by collection.
|
||||
*
|
||||
* @var \Drupal\Core\Config\StorageInterface[]
|
||||
*/
|
||||
protected $targetStorages;
|
||||
|
||||
/**
|
||||
* The configuration manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigManagerInterface
|
||||
*/
|
||||
protected $configManager;
|
||||
|
||||
/**
|
||||
* List of changes to between the source storage and the target storage.
|
||||
*
|
||||
* The list is keyed by storage collection name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $changelist;
|
||||
|
@ -39,6 +63,8 @@ class StorageComparer implements StorageComparerInterface {
|
|||
/**
|
||||
* Sorted list of all the configuration object names in the source storage.
|
||||
*
|
||||
* The list is keyed by storage collection name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceNames = array();
|
||||
|
@ -46,6 +72,8 @@ class StorageComparer implements StorageComparerInterface {
|
|||
/**
|
||||
* Sorted list of all the configuration object names in the target storage.
|
||||
*
|
||||
* The list is keyed by storage collection name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $targetNames = array();
|
||||
|
@ -53,6 +81,8 @@ class StorageComparer implements StorageComparerInterface {
|
|||
/**
|
||||
* The source configuration data keyed by name.
|
||||
*
|
||||
* The data is keyed by storage collection name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceData = array();
|
||||
|
@ -60,6 +90,8 @@ class StorageComparer implements StorageComparerInterface {
|
|||
/**
|
||||
* The target configuration data keyed by name.
|
||||
*
|
||||
* The data is keyed by storage collection name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $targetData = array();
|
||||
|
@ -71,25 +103,44 @@ class StorageComparer implements StorageComparerInterface {
|
|||
* Storage object used to read configuration.
|
||||
* @param \Drupal\Core\Config\StorageInterface $target_storage
|
||||
* Storage object used to write configuration.
|
||||
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
|
||||
* The configuration manager.
|
||||
*/
|
||||
public function __construct(StorageInterface $source_storage, StorageInterface $target_storage) {
|
||||
public function __construct(StorageInterface $source_storage, StorageInterface $target_storage, ConfigManagerInterface $config_manager) {
|
||||
$this->sourceStorage = $source_storage;
|
||||
$this->targetStorage = $target_storage;
|
||||
$this->changelist = $this->getEmptyChangelist();
|
||||
$this->configManager = $config_manager;
|
||||
$this->changelist[StorageInterface::DEFAULT_COLLECTION] = $this->getEmptyChangelist();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSourceStorage() {
|
||||
return $this->sourceStorage;
|
||||
public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if (!isset($this->sourceStorages[$collection])) {
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->sourceStorages[$collection] = $this->sourceStorage;
|
||||
}
|
||||
else {
|
||||
$this->sourceStorages[$collection] = $this->sourceStorage->createCollection($collection);
|
||||
}
|
||||
}
|
||||
return $this->sourceStorages[$collection];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTargetStorage() {
|
||||
return $this->targetStorage;
|
||||
public function getTargetStorage($collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if (!isset($this->targetStorages[$collection])) {
|
||||
if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
||||
$this->targetStorages[$collection] = $this->targetStorage;
|
||||
}
|
||||
else {
|
||||
$this->targetStorages[$collection] = $this->targetStorage->createCollection($collection);
|
||||
}
|
||||
}
|
||||
return $this->targetStorages[$collection];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,16 +158,18 @@ class StorageComparer implements StorageComparerInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChangelist($op = NULL) {
|
||||
public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
if ($op) {
|
||||
return $this->changelist[$op];
|
||||
return $this->changelist[$collection][$op];
|
||||
}
|
||||
return $this->changelist;
|
||||
return $this->changelist[$collection];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds changes to the changelist.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to add changes for.
|
||||
* @param string $op
|
||||
* The change operation performed. Either delete, create, rename, or update.
|
||||
* @param array $changes
|
||||
|
@ -125,16 +178,16 @@ class StorageComparer implements StorageComparerInterface {
|
|||
* Array to sort that can be used to sort the changelist. This array must
|
||||
* contain all the items that are in the change list.
|
||||
*/
|
||||
protected function addChangeList($op, array $changes, array $sort_order = NULL) {
|
||||
protected function addChangeList($collection, $op, array $changes, array $sort_order = NULL) {
|
||||
// Only add changes that aren't already listed.
|
||||
$changes = array_diff($changes, $this->changelist[$op]);
|
||||
$this->changelist[$op] = array_merge($this->changelist[$op], $changes);
|
||||
$changes = array_diff($changes, $this->changelist[$collection][$op]);
|
||||
$this->changelist[$collection][$op] = array_merge($this->changelist[$collection][$op], $changes);
|
||||
if (isset($sort_order)) {
|
||||
$count = count($this->changelist[$op]);
|
||||
$count = count($this->changelist[$collection][$op]);
|
||||
// Sort the changlist in the same order as the $sort_order array and
|
||||
// ensure the array is keyed from 0.
|
||||
$this->changelist[$op] = array_values(array_intersect($sort_order, $this->changelist[$op]));
|
||||
if ($count != count($this->changelist[$op])) {
|
||||
$this->changelist[$collection][$op] = array_values(array_intersect($sort_order, $this->changelist[$collection][$op]));
|
||||
if ($count != count($this->changelist[$collection][$op])) {
|
||||
throw new \InvalidArgumentException(String::format('Sorting the @op changelist should not change its length.', array('@op' => $op)));
|
||||
}
|
||||
}
|
||||
|
@ -144,13 +197,20 @@ class StorageComparer implements StorageComparerInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function createChangelist() {
|
||||
$this->getAndSortConfigData();
|
||||
$this->addChangelistCreate();
|
||||
$this->addChangelistUpdate();
|
||||
$this->addChangelistDelete();
|
||||
$this->addChangelistRename();
|
||||
$this->sourceData = NULL;
|
||||
$this->targetData = NULL;
|
||||
foreach ($this->getAllCollectionNames() as $collection) {
|
||||
$this->changelist[$collection] = $this->getEmptyChangelist();
|
||||
$this->getAndSortConfigData($collection);
|
||||
$this->addChangelistCreate($collection);
|
||||
$this->addChangelistUpdate($collection);
|
||||
$this->addChangelistDelete($collection);
|
||||
// Only collections that support configuration entities can have renames.
|
||||
if ($this->configManager->supportsConfigurationEntities($collection)) {
|
||||
$this->addChangelistRename($collection);
|
||||
}
|
||||
// Only need data whilst calculating changelists. Free up the memory.
|
||||
$this->sourceData = NULL;
|
||||
$this->targetData = NULL;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -160,10 +220,13 @@ class StorageComparer implements StorageComparerInterface {
|
|||
* The list of deletes is sorted so that dependencies are deleted after
|
||||
* configuration entities that depend on them. For example, field instances
|
||||
* should be deleted after fields.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
*/
|
||||
protected function addChangelistDelete() {
|
||||
$deletes = array_diff(array_reverse($this->targetNames), $this->sourceNames);
|
||||
$this->addChangeList('delete', $deletes);
|
||||
protected function addChangelistDelete($collection) {
|
||||
$deletes = array_diff(array_reverse($this->targetNames[$collection]), $this->sourceNames[$collection]);
|
||||
$this->addChangeList($collection, 'delete', $deletes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -172,10 +235,13 @@ class StorageComparer implements StorageComparerInterface {
|
|||
* The list of creates is sorted so that dependencies are created before
|
||||
* configuration entities that depend on them. For example, fields
|
||||
* should be created before field instances.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
*/
|
||||
protected function addChangelistCreate() {
|
||||
$creates = array_diff($this->sourceNames, $this->targetNames);
|
||||
$this->addChangeList('create', $creates);
|
||||
protected function addChangelistCreate($collection) {
|
||||
$creates = array_diff($this->sourceNames[$collection], $this->targetNames[$collection]);
|
||||
$this->addChangeList($collection, 'create', $creates);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -184,19 +250,22 @@ class StorageComparer implements StorageComparerInterface {
|
|||
* The list of updates is sorted so that dependencies are created before
|
||||
* configuration entities that depend on them. For example, fields
|
||||
* should be updated before field instances.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
*/
|
||||
protected function addChangelistUpdate() {
|
||||
protected function addChangelistUpdate($collection) {
|
||||
$recreates = array();
|
||||
foreach (array_intersect($this->sourceNames, $this->targetNames) as $name) {
|
||||
if ($this->sourceData[$name] !== $this->targetData[$name]) {
|
||||
if (isset($this->sourceData[$name]['uuid']) && $this->sourceData[$name]['uuid'] != $this->targetData[$name]['uuid']) {
|
||||
foreach (array_intersect($this->sourceNames[$collection], $this->targetNames[$collection]) as $name) {
|
||||
if ($this->sourceData[$collection][$name] !== $this->targetData[$collection][$name]) {
|
||||
if (isset($this->sourceData[$collection][$name]['uuid']) && $this->sourceData[$collection][$name]['uuid'] != $this->targetData[$collection][$name]['uuid']) {
|
||||
// The entity has the same file as an existing entity but the UUIDs do
|
||||
// not match. This means that the entity has been recreated so config
|
||||
// synchronisation should do the same.
|
||||
$recreates[] = $name;
|
||||
}
|
||||
else {
|
||||
$this->addChangeList('update', array($name));
|
||||
$this->addChangeList($collection, 'update', array($name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -204,8 +273,8 @@ class StorageComparer implements StorageComparerInterface {
|
|||
if (!empty($recreates)) {
|
||||
// Recreates should become deletes and creates. Deletes should be ordered
|
||||
// so that dependencies are deleted first.
|
||||
$this->addChangeList('create', $recreates, $this->sourceNames);
|
||||
$this->addChangeList('delete', $recreates, array_reverse($this->targetNames));
|
||||
$this->addChangeList($collection, 'create', $recreates, $this->sourceNames[$collection]);
|
||||
$this->addChangeList($collection, 'delete', $recreates, array_reverse($this->targetNames[$collection]));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -216,17 +285,20 @@ class StorageComparer implements StorageComparerInterface {
|
|||
* The list of renames is created from the different source and target names
|
||||
* with same UUID. These changes will be removed from the create and delete
|
||||
* lists.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
*/
|
||||
protected function addChangelistRename() {
|
||||
protected function addChangelistRename($collection) {
|
||||
// Renames will be present in both the create and delete lists.
|
||||
$create_list = $this->getChangelist('create');
|
||||
$delete_list = $this->getChangelist('delete');
|
||||
$create_list = $this->getChangelist('create', $collection);
|
||||
$delete_list = $this->getChangelist('delete', $collection);
|
||||
if (empty($create_list) || empty($delete_list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$create_uuids = array();
|
||||
foreach ($this->sourceData as $id => $data) {
|
||||
foreach ($this->sourceData[$collection] as $id => $data) {
|
||||
if (isset($data['uuid']) && in_array($id, $create_list)) {
|
||||
$create_uuids[$data['uuid']] = $id;
|
||||
}
|
||||
|
@ -245,50 +317,52 @@ class StorageComparer implements StorageComparerInterface {
|
|||
// Node type is a good example of a configuration entity that renames other
|
||||
// configuration when it is renamed.
|
||||
// @see \Drupal\node\Entity\NodeType::postSave()
|
||||
foreach ($this->targetNames as $name) {
|
||||
$data = $this->targetData[$name];
|
||||
foreach ($this->targetNames[$collection] as $name) {
|
||||
$data = $this->targetData[$collection][$name];
|
||||
if (isset($data['uuid']) && isset($create_uuids[$data['uuid']])) {
|
||||
// Remove the item from the create list.
|
||||
$this->removeFromChangelist('create', $create_uuids[$data['uuid']]);
|
||||
$this->removeFromChangelist($collection, 'create', $create_uuids[$data['uuid']]);
|
||||
// Remove the item from the delete list.
|
||||
$this->removeFromChangelist('delete', $name);
|
||||
$this->removeFromChangelist($collection, 'delete', $name);
|
||||
// Create the rename name.
|
||||
$renames[] = $this->createRenameName($name, $create_uuids[$data['uuid']]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addChangeList('rename', $renames);
|
||||
$this->addChangeList($collection, 'rename', $renames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the entry from the given operation changelist for the given name.
|
||||
*
|
||||
* @param string $collection
|
||||
* The storage collection to operate on.
|
||||
* @param string $op
|
||||
* The changelist to act on. Either delete, create, rename or update.
|
||||
* @param string $name
|
||||
* The name of the configuration to remove.
|
||||
*/
|
||||
protected function removeFromChangelist($op, $name) {
|
||||
$key = array_search($name, $this->changelist[$op]);
|
||||
protected function removeFromChangelist($collection, $op, $name) {
|
||||
$key = array_search($name, $this->changelist[$collection][$op]);
|
||||
if ($key !== FALSE) {
|
||||
unset($this->changelist[$op][$key]);
|
||||
unset($this->changelist[$collection][$op][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function moveRenameToUpdate($rename) {
|
||||
public function moveRenameToUpdate($rename, $collection = StorageInterface::DEFAULT_COLLECTION) {
|
||||
$names = $this->extractRenameNames($rename);
|
||||
$this->removeFromChangelist('rename', $rename);
|
||||
$this->addChangeList('update', array($names['new_name']), $this->sourceNames);
|
||||
$this->removeFromChangelist($collection, 'rename', $rename);
|
||||
$this->addChangeList($collection, 'update', array($names['new_name']), $this->sourceNames[$collection]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function reset() {
|
||||
$this->changelist = $this->getEmptyChangelist();
|
||||
$this->changelist = array(StorageInterface::DEFAULT_COLLECTION => $this->getEmptyChangelist());
|
||||
$this->sourceNames = $this->targetNames = array();
|
||||
return $this->createChangelist();
|
||||
}
|
||||
|
@ -296,10 +370,12 @@ class StorageComparer implements StorageComparerInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasChanges($ops = array('delete', 'create', 'update', 'rename')) {
|
||||
foreach ($ops as $op) {
|
||||
if (!empty($this->changelist[$op])) {
|
||||
return TRUE;
|
||||
public function hasChanges() {
|
||||
foreach ($this->getAllCollectionNames() as $collection) {
|
||||
foreach (array('delete', 'create', 'update', 'rename') as $op) {
|
||||
if (!empty($this->changelist[$collection][$op])) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
|
@ -317,12 +393,24 @@ class StorageComparer implements StorageComparerInterface {
|
|||
/**
|
||||
* Gets and sorts configuration data from the source and target storages.
|
||||
*/
|
||||
protected function getAndSortConfigData() {
|
||||
$this->targetData = $this->targetStorage->readMultiple($this->targetStorage->listAll());
|
||||
$this->sourceData = $this->sourceStorage->readMultiple($this->sourceStorage->listAll());
|
||||
$dependency_manager = new ConfigDependencyManager();
|
||||
$this->targetNames = $dependency_manager->setData($this->targetData)->sortAll();
|
||||
$this->sourceNames = $dependency_manager->setData($this->sourceData)->sortAll();
|
||||
protected function getAndSortConfigData($collection) {
|
||||
$source_storage = $this->getSourceStorage($collection);
|
||||
$target_storage = $this->getTargetStorage($collection);
|
||||
$target_names = $target_storage->listAll();
|
||||
$source_names = $source_storage->listAll();
|
||||
$this->targetData[$collection] = $target_storage->readMultiple($target_names);
|
||||
$this->sourceData[$collection] = $source_storage->readMultiple($source_names);
|
||||
// If the collection only supports simple configuration do not use
|
||||
// configuration dependencies.
|
||||
if ($this->configManager->supportsConfigurationEntities($collection)) {
|
||||
$dependency_manager = new ConfigDependencyManager();
|
||||
$this->targetNames[$collection] = $dependency_manager->setData($this->targetData[$collection])->sortAll();
|
||||
$this->sourceNames[$collection] = $dependency_manager->setData($this->sourceData[$collection])->sortAll();
|
||||
}
|
||||
else {
|
||||
$this->targetNames[$collection] = $target_names;
|
||||
$this->sourceNames[$collection] = $source_names;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -352,4 +440,16 @@ class StorageComparer implements StorageComparerInterface {
|
|||
'new_name' => $names[1],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAllCollectionNames($include_default = TRUE) {
|
||||
$collections = array_unique(array_merge($this->sourceStorage->getAllCollectionNames(), $this->targetStorage->getAllCollectionNames()));
|
||||
if ($include_default) {
|
||||
array_unshift($collections, StorageInterface::DEFAULT_COLLECTION);
|
||||
}
|
||||
return $collections;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,18 +15,26 @@ interface StorageComparerInterface {
|
|||
/**
|
||||
* Gets the configuration source storage.
|
||||
*
|
||||
* @param string $collection
|
||||
* (optional) The storage collection to use. Defaults to the
|
||||
* default collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* Storage object used to read configuration.
|
||||
*/
|
||||
public function getSourceStorage();
|
||||
public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Gets the configuration target storage.
|
||||
*
|
||||
* @param string $collection
|
||||
* (optional) The storage collection to use. Defaults to the
|
||||
* default collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* Storage object used to write configuration.
|
||||
*/
|
||||
public function getTargetStorage();
|
||||
public function getTargetStorage($collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Gets an empty changelist.
|
||||
|
@ -42,11 +50,14 @@ interface StorageComparerInterface {
|
|||
* @param string $op
|
||||
* (optional) A change operation. Either delete, create or update. If
|
||||
* supplied the returned list will be limited to this operation.
|
||||
* @param string $collection
|
||||
* (optional) The collection to get the changelist for. Defaults to the
|
||||
* default collection.
|
||||
*
|
||||
* @return array
|
||||
* An array of config changes that are yet to be imported.
|
||||
*/
|
||||
public function getChangelist($op = NULL);
|
||||
public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Recalculates the differences.
|
||||
|
@ -63,14 +74,10 @@ interface StorageComparerInterface {
|
|||
*
|
||||
* @see \Drupal\Core\Config\StorageComparerInterface::createChangelist().
|
||||
*
|
||||
* @param array $ops
|
||||
* The operations to check for changes. Defaults to all operations, i.e.
|
||||
* array('delete', 'create', 'update').
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there are changes to process and FALSE if not.
|
||||
*/
|
||||
public function hasChanges($ops = array('delete', 'create', 'update'));
|
||||
public function hasChanges();
|
||||
|
||||
/**
|
||||
* Validates that the system.site::uuid in the source and target match.
|
||||
|
@ -85,10 +92,13 @@ interface StorageComparerInterface {
|
|||
*
|
||||
* @param string $rename
|
||||
* The rename name, as provided by ConfigImporter::createRenameName().
|
||||
* @param string $collection
|
||||
* (optional) The collection where the configuration is stored. Defaults to
|
||||
* the default collection.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigImporter::createRenameName()
|
||||
*/
|
||||
public function moveRenameToUpdate($rename);
|
||||
public function moveRenameToUpdate($rename, $collection = StorageInterface::DEFAULT_COLLECTION);
|
||||
|
||||
/**
|
||||
* Extracts old and new configuration names from a configuration change name.
|
||||
|
@ -106,4 +116,15 @@ interface StorageComparerInterface {
|
|||
*/
|
||||
public function extractRenameNames($name);
|
||||
|
||||
/**
|
||||
* Gets the existing collections from both the target and source storage.
|
||||
*
|
||||
* @param bool $include_default
|
||||
* (optional) Include the default collection. Defaults to TRUE.
|
||||
*
|
||||
* @return array
|
||||
* An array of existing collection names.
|
||||
*/
|
||||
public function getAllCollectionNames($include_default = TRUE);
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,11 @@ namespace Drupal\Core\Config;
|
|||
*/
|
||||
interface StorageInterface {
|
||||
|
||||
/**
|
||||
* The default collection name.
|
||||
*/
|
||||
const DEFAULT_COLLECTION = '';
|
||||
|
||||
/**
|
||||
* Returns whether a configuration object exists.
|
||||
*
|
||||
|
@ -156,4 +161,46 @@ interface StorageInterface {
|
|||
*/
|
||||
public function deleteAll($prefix = '');
|
||||
|
||||
/**
|
||||
* Creates a collection on the storage.
|
||||
*
|
||||
* A configuration storage can contain multiple sets of configuration objects
|
||||
* in partitioned collections. The collection name identifies the current
|
||||
* collection used.
|
||||
*
|
||||
* Implementations of this method must provide a new instance to avoid side
|
||||
* effects caused by the fact that Config objects have their storage injected.
|
||||
*
|
||||
* @param string $collection
|
||||
* The collection name. Valid collection names conform to the following
|
||||
* regex [a-zA-Z_.]. A storage does not need to have a collection set.
|
||||
* However, if a collection is set, then storage should use it to store
|
||||
* configuration in a way that allows retrieval of configuration for a
|
||||
* particular collection.
|
||||
*
|
||||
* @return \Drupal\Core\Config\StorageInterface
|
||||
* A new instance of the storage backend with the collection set.
|
||||
*/
|
||||
public function createCollection($collection);
|
||||
|
||||
/**
|
||||
* Gets the existing collections.
|
||||
*
|
||||
* A configuration storage can contain multiple sets of configuration objects
|
||||
* in partitioned collections. The collection key name identifies the current
|
||||
* collection used.
|
||||
*
|
||||
* @return array
|
||||
* An array of existing collection names.
|
||||
*/
|
||||
public function getAllCollectionNames();
|
||||
|
||||
/**
|
||||
* Gets the name of the current collection the storage is using.
|
||||
*
|
||||
* @return string
|
||||
* The current collection name.
|
||||
*/
|
||||
public function getCollectionName();
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,14 @@ config.diff:
|
|||
requirements:
|
||||
_permission: 'synchronize configuration'
|
||||
|
||||
config.diff_collection:
|
||||
path: '/admin/config/development/configuration/sync/diff_collection/{collection}/{source_name}/{target_name}'
|
||||
defaults:
|
||||
_content: '\Drupal\config\Controller\ConfigController::diff'
|
||||
target_name: NULL
|
||||
requirements:
|
||||
_permission: 'synchronize configuration'
|
||||
|
||||
config.export_download:
|
||||
path: '/admin/config/development/configuration/full/export-download'
|
||||
defaults:
|
||||
|
|
|
@ -85,9 +85,15 @@ class ConfigController implements ContainerInjectionInterface {
|
|||
file_unmanaged_delete(file_directory_temp() . '/config.tar.gz');
|
||||
|
||||
$archiver = new ArchiveTar(file_directory_temp() . '/config.tar.gz', 'gz');
|
||||
foreach (\Drupal::service('config.storage')->listAll() as $name) {
|
||||
foreach ($this->targetStorage->listAll() as $name) {
|
||||
$archiver->addString("$name.yml", Yaml::encode(\Drupal::config($name)->get()));
|
||||
}
|
||||
foreach ($this->targetStorage->getAllCollectionNames() as $collection) {
|
||||
$collection_storage = $this->targetStorage->createCollection($collection);
|
||||
foreach ($collection_storage->listAll() as $name) {
|
||||
$archiver->addString(str_replace('.', '/', $collection) . "/$name.yml", Yaml::encode($collection_storage->read($name)));
|
||||
}
|
||||
}
|
||||
|
||||
$request = new Request(array('file' => 'config.tar.gz'));
|
||||
return $this->fileDownloadController->download($request, 'temporary');
|
||||
|
@ -96,15 +102,23 @@ class ConfigController implements ContainerInjectionInterface {
|
|||
/**
|
||||
* Shows diff of specificed configuration file.
|
||||
*
|
||||
* @param string $config_file
|
||||
* @param string $source_name
|
||||
* The name of the configuration file.
|
||||
* @param string $target_name
|
||||
* (optional) The name of the target configuration file if different from
|
||||
* the $source_name.
|
||||
* @param string $collection
|
||||
* (optional) The configuration collection name. Defaults to the default
|
||||
* collection.
|
||||
*
|
||||
* @return string
|
||||
* Table showing a two-way diff between the active and staged configuration.
|
||||
*/
|
||||
public function diff($source_name, $target_name = NULL) {
|
||||
|
||||
$diff = $this->configManager->diff($this->targetStorage, $this->sourceStorage, $source_name, $target_name);
|
||||
public function diff($source_name, $target_name = NULL, $collection = NULL) {
|
||||
if (!isset($collection)) {
|
||||
$collection = StorageInterface::DEFAULT_COLLECTION;
|
||||
}
|
||||
$diff = $this->configManager->diff($this->targetStorage, $this->sourceStorage, $source_name, $target_name, $collection);
|
||||
$formatter = new \DrupalDiffFormatter();
|
||||
$formatter->show_header = FALSE;
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ class ConfigSync extends FormBase {
|
|||
);
|
||||
|
||||
$source_list = $this->sourceStorage->listAll();
|
||||
$storage_comparer = new StorageComparer($this->sourceStorage, $this->targetStorage);
|
||||
$storage_comparer = new StorageComparer($this->sourceStorage, $this->targetStorage, $this->configManager);
|
||||
if (empty($source_list) || !$storage_comparer->createChangelist()->hasChanges()) {
|
||||
$form['no_changes'] = array(
|
||||
'#type' => 'table',
|
||||
|
@ -184,71 +184,86 @@ class ConfigSync extends FormBase {
|
|||
// Add the AJAX library to the form for dialog support.
|
||||
$form['#attached']['library'][] = 'core/drupal.ajax';
|
||||
|
||||
foreach ($storage_comparer->getChangelist() as $config_change_type => $config_names) {
|
||||
if (empty($config_names)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @todo A table caption would be more appropriate, but does not have the
|
||||
// visual importance of a heading.
|
||||
$form[$config_change_type]['heading'] = array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'h3',
|
||||
);
|
||||
switch ($config_change_type) {
|
||||
case 'create':
|
||||
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count new', '@count new');
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count changed', '@count changed');
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count removed', '@count removed');
|
||||
break;
|
||||
|
||||
case 'rename':
|
||||
$form[$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count renamed', '@count renamed');
|
||||
break;
|
||||
}
|
||||
$form[$config_change_type]['list'] = array(
|
||||
'#type' => 'table',
|
||||
'#header' => array('Name', 'Operations'),
|
||||
);
|
||||
|
||||
foreach ($config_names as $config_name) {
|
||||
if ($config_change_type == 'rename') {
|
||||
$names = $storage_comparer->extractRenameNames($config_name);
|
||||
$href = $this->urlGenerator->getPathFromRoute('config.diff', array('source_name' => $names['old_name'], 'target_name' => $names['new_name']));
|
||||
$config_name = $this->t('!source_name to !target_name', array('!source_name' => $names['old_name'], '!target_name' => $names['new_name']));
|
||||
}
|
||||
else {
|
||||
$href = $this->urlGenerator->getPathFromRoute('config.diff', array('source_name' => $config_name));
|
||||
}
|
||||
$links['view_diff'] = array(
|
||||
'title' => $this->t('View differences'),
|
||||
'href' => $href,
|
||||
'attributes' => array(
|
||||
'class' => array('use-ajax'),
|
||||
'data-accepts' => 'application/vnd.drupal-modal',
|
||||
'data-dialog-options' => json_encode(array(
|
||||
'width' => 700
|
||||
)),
|
||||
),
|
||||
foreach ($storage_comparer->getAllCollectionNames() as $collection) {
|
||||
if ($collection != StorageInterface::DEFAULT_COLLECTION) {
|
||||
$form[$collection]['collection_heading'] = array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'h2',
|
||||
'#value' => $this->t('!collection configuration collection', array('!collection' => $collection)),
|
||||
);
|
||||
$form[$config_change_type]['list']['#rows'][] = array(
|
||||
'name' => $config_name,
|
||||
'operations' => array(
|
||||
'data' => array(
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
}
|
||||
foreach ($storage_comparer->getChangelist(NULL, $collection) as $config_change_type => $config_names) {
|
||||
if (empty($config_names)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @todo A table caption would be more appropriate, but does not have the
|
||||
// visual importance of a heading.
|
||||
$form[$collection][$config_change_type]['heading'] = array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'h3',
|
||||
);
|
||||
switch ($config_change_type) {
|
||||
case 'create':
|
||||
$form[$collection][$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count new', '@count new');
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
$form[$collection][$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count changed', '@count changed');
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$form[$collection][$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count removed', '@count removed');
|
||||
break;
|
||||
|
||||
case 'rename':
|
||||
$form[$collection][$config_change_type]['heading']['#value'] = format_plural(count($config_names), '@count renamed', '@count renamed');
|
||||
break;
|
||||
}
|
||||
$form[$collection][$config_change_type]['list'] = array(
|
||||
'#type' => 'table',
|
||||
'#header' => array('Name', 'Operations'),
|
||||
);
|
||||
|
||||
foreach ($config_names as $config_name) {
|
||||
if ($config_change_type == 'rename') {
|
||||
$names = $storage_comparer->extractRenameNames($config_name);
|
||||
$route_options = array('source_name' => $names['old_name'], 'target_name' => $names['new_name']);
|
||||
$config_name = $this->t('!source_name to !target_name', array('!source_name' => $names['old_name'], '!target_name' => $names['new_name']));
|
||||
}
|
||||
else {
|
||||
$route_options = array('source_name' => $config_name);
|
||||
}
|
||||
if ($collection != StorageInterface::DEFAULT_COLLECTION) {
|
||||
$route_options['collection'] = $collection;
|
||||
$href = $this->urlGenerator->getPathFromRoute('config.diff_collection', $route_options);
|
||||
}
|
||||
else {
|
||||
$href = $this->urlGenerator->getPathFromRoute('config.diff', $route_options);
|
||||
}
|
||||
$links['view_diff'] = array(
|
||||
'title' => $this->t('View differences'),
|
||||
'href' => $href,
|
||||
'attributes' => array(
|
||||
'class' => array('use-ajax'),
|
||||
'data-accepts' => 'application/vnd.drupal-modal',
|
||||
'data-dialog-options' => json_encode(array(
|
||||
'width' => 700
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
$form[$collection][$config_change_type]['list']['#rows'][] = array(
|
||||
'name' => $config_name,
|
||||
'operations' => array(
|
||||
'data' => array(
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
|
|
@ -113,4 +113,36 @@ class ConfigDiffTest extends DrupalUnitTestBase {
|
|||
$this->assertEqual(count($diff->edits), 2, 'There are two items in the diff.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests calculating the difference between two sets of config collections.
|
||||
*/
|
||||
function testCollectionDiff() {
|
||||
/** @var \Drupal\Core\Config\StorageInterface $active */
|
||||
$active = $this->container->get('config.storage');
|
||||
/** @var \Drupal\Core\Config\StorageInterface $staging */
|
||||
$staging = $this->container->get('config.storage.staging');
|
||||
$active_test_collection = $active->createCollection('test');
|
||||
$staging_test_collection = $staging->createCollection('test');
|
||||
|
||||
$config_name = 'config_test.test';
|
||||
$data = array('foo' => 'bar');
|
||||
|
||||
$active->write($config_name, $data);
|
||||
$staging->write($config_name, $data);
|
||||
$active_test_collection->write($config_name, $data);
|
||||
$staging_test_collection->write($config_name, array('foo' => 'baz'));
|
||||
|
||||
// Test the fields match in the default collection diff.
|
||||
$diff = \Drupal::service('config.manager')->diff($active, $staging, $config_name);
|
||||
$this->assertEqual($diff->edits[0]->type, 'copy', 'The first item in the diff is a copy.');
|
||||
$this->assertEqual(count($diff->edits), 1, 'There is one item in the diff');
|
||||
|
||||
// Test that the differences are detected when diffing the collection.
|
||||
$diff = \Drupal::service('config.manager')->diff($active, $staging, $config_name, NULL, 'test');
|
||||
$this->assertEqual($diff->edits[0]->type, 'change', 'The second item in the diff is a copy.');
|
||||
$this->assertEqual($diff->edits[0]->orig, array('foo: bar'));
|
||||
$this->assertEqual($diff->edits[0]->closing, array('foo: baz'));
|
||||
$this->assertEqual($diff->edits[1]->type, 'copy', 'The second item in the diff is a copy.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\config\Tests;
|
||||
|
||||
use Drupal\Core\Archiver\ArchiveTar;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
|
@ -130,4 +131,117 @@ class ConfigExportImportUITest extends WebTestBase {
|
|||
$this->drupalGet('node/add');
|
||||
$this->assertFieldByName("{$this->field->name}[0][value]", '', 'Widget is displayed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests an export and import of collections.
|
||||
*/
|
||||
public function testExportImportCollections() {
|
||||
|
||||
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
|
||||
$active_storage = \Drupal::service('config.storage');
|
||||
$test1_storage = $active_storage->createCollection('collection.test1');
|
||||
$test1_storage->write('config_test.create', array('foo' => 'bar'));
|
||||
$test1_storage->write('config_test.update', array('foo' => 'bar'));
|
||||
$test2_storage = $active_storage->createCollection('collection.test2');
|
||||
$test2_storage->write('config_test.another_create', array('foo' => 'bar'));
|
||||
$test2_storage->write('config_test.another_update', array('foo' => 'bar'));
|
||||
|
||||
// Export the configuration.
|
||||
$this->drupalPostForm('admin/config/development/configuration/full/export', array(), 'Export');
|
||||
$this->tarball = $this->drupalGetContent();
|
||||
$filename = file_directory_temp() .'/' . $this->randomName();
|
||||
file_put_contents($filename, $this->tarball);
|
||||
|
||||
// Set up the active storage collections to test import.
|
||||
$test1_storage->delete('config_test.create');
|
||||
$test1_storage->write('config_test.update', array('foo' => 'baz'));
|
||||
$test1_storage->write('config_test.delete', array('foo' => 'bar'));
|
||||
$test2_storage->delete('config_test.another_create');
|
||||
$test2_storage->write('config_test.another_update', array('foo' => 'baz'));
|
||||
$test2_storage->write('config_test.another_delete', array('foo' => 'bar'));
|
||||
|
||||
// Create a snapshot.
|
||||
$snapshot_storage = \Drupal::service('config.storage.snapshot');
|
||||
\Drupal::service('config.manager')->createSnapshot($active_storage, $snapshot_storage);
|
||||
|
||||
// Ensure that the snapshot has the expected collection data before import.
|
||||
$test1_snapshot = $snapshot_storage->createCollection('collection.test1');
|
||||
$data = $test1_snapshot->read('config_test.delete');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.delete in collection.test1 exists in the snapshot storage.');
|
||||
$data = $test1_snapshot->read('config_test.update');
|
||||
$this->assertEqual($data, array('foo' => 'baz'), 'The config_test.update in collection.test1 exists in the snapshot storage.');
|
||||
$this->assertFalse($test1_snapshot->read('config_test.create'), 'The config_test.create in collection.test1 does not exist in the snapshot storage.');
|
||||
$test2_snapshot = $snapshot_storage->createCollection('collection.test2');
|
||||
$data = $test2_snapshot->read('config_test.another_delete');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_delete in collection.test2 exists in the snapshot storage.');
|
||||
$data = $test2_snapshot->read('config_test.another_update');
|
||||
$this->assertEqual($data, array('foo' => 'baz'), 'The config_test.another_update in collection.test2 exists in the snapshot storage.');
|
||||
$this->assertFalse($test2_snapshot->read('config_test.another_create'), 'The config_test.another_create in collection.test2 does not exist in the snapshot storage.');
|
||||
|
||||
// Create the tar contains the expected contect for the collections.
|
||||
$tar = new ArchiveTar($filename, 'gz');
|
||||
$content_list = $tar->listContent();
|
||||
// Convert the list of files into something easy to search.
|
||||
$files = array();
|
||||
foreach ($content_list as $file) {
|
||||
$files[] = $file['filename'];
|
||||
}
|
||||
$this->assertTrue(in_array('collection/test1/config_test.create.yml', $files), 'Config export contains collection/test1/config_test.create.yml.');
|
||||
$this->assertTrue(in_array('collection/test2/config_test.another_create.yml', $files), 'Config export contains collection/test2/config_test.another_create.yml.');
|
||||
$this->assertTrue(in_array('collection/test1/config_test.update.yml', $files), 'Config export contains collection/test1/config_test.update.yml.');
|
||||
$this->assertTrue(in_array('collection/test2/config_test.another_update.yml', $files), 'Config export contains collection/test2/config_test.another_update.yml.');
|
||||
$this->assertFalse(in_array('collection/test1/config_test.delete.yml', $files), 'Config export does not contain collection/test1/config_test.delete.yml.');
|
||||
$this->assertFalse(in_array('collection/test2/config_test.another_delete.yml', $files), 'Config export does not contain collection/test2/config_test.another_delete.yml.');
|
||||
|
||||
$this->drupalPostForm('admin/config/development/configuration/full/import', array('files[import_tarball]' => $filename), 'Upload');
|
||||
// Verify that there are configuration differences to import.
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
$this->assertNoText(t('There are no configuration changes.'));
|
||||
$this->assertText(t('!collection configuration collection', array('!collection' => 'collection.test1')));
|
||||
$this->assertText(t('!collection configuration collection', array('!collection' => 'collection.test2')));
|
||||
$this->assertText('config_test.create');
|
||||
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test1/config_test.create');
|
||||
$this->assertText('config_test.update');
|
||||
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test1/config_test.update');
|
||||
$this->assertText('config_test.delete');
|
||||
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test1/config_test.delete');
|
||||
$this->assertText('config_test.another_create');
|
||||
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test2/config_test.another_create');
|
||||
$this->assertText('config_test.another_update');
|
||||
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test2/config_test.another_update');
|
||||
$this->assertText('config_test.another_delete');
|
||||
$this->assertLinkByHref('admin/config/development/configuration/sync/diff_collection/collection.test2/config_test.another_delete');
|
||||
|
||||
$this->drupalPostForm(NULL, array(), 'Import all');
|
||||
$this->assertText(t('There are no configuration changes.'));
|
||||
|
||||
// Test data in collections.
|
||||
$data = $test1_storage->read('config_test.create');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.create in collection.test1 has been created.');
|
||||
$data = $test1_storage->read('config_test.update');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.update in collection.test1 has been updated.');
|
||||
$this->assertFalse($test1_storage->read('config_test.delete'), 'The config_test.delete in collection.test1 has been deleted.');
|
||||
|
||||
$data = $test2_storage->read('config_test.another_create');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_create in collection.test2 has been created.');
|
||||
$data = $test2_storage->read('config_test.another_update');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_update in collection.test2 has been updated.');
|
||||
$this->assertFalse($test2_storage->read('config_test.another_delete'), 'The config_test.another_delete in collection.test2 has been deleted.');
|
||||
|
||||
// Ensure that the snapshot has been updated with the collection data.
|
||||
$snapshot_storage = \Drupal::service('config.storage.snapshot');
|
||||
$test1_snapshot = $snapshot_storage->createCollection('collection.test1');
|
||||
$data = $test1_snapshot->read('config_test.create');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.create in collection.test1 has been created in the snapshot storage.');
|
||||
$data = $test1_snapshot->read('config_test.update');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.update in collection.test1 has been updated in the snapshot storage.');
|
||||
$this->assertFalse($test1_snapshot->read('config_test.delete'), 'The config_test.delete in collection.test1 does not exist in the snapshot storage.');
|
||||
$test2_snapshot = $snapshot_storage->createCollection('collection.test2');
|
||||
$data = $test2_snapshot->read('config_test.another_create');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_create in collection.test2 has been created in the snapshot storage.');
|
||||
$data = $test2_snapshot->read('config_test.another_update');
|
||||
$this->assertEqual($data, array('foo' => 'bar'), 'The config_test.another_update in collection.test2 has been updated in the snapshot storage.');
|
||||
$this->assertFalse($test2_snapshot->read('config_test.another_delete'), 'The config_test.another_delete in collection.test2 does not exist in the snapshot storage.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -111,7 +111,8 @@ class ConfigImportAllTest extends ModuleTestBase {
|
|||
// Ensure that we have no configuration changes to import.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.staging'),
|
||||
$this->container->get('config.storage')
|
||||
$this->container->get('config.storage'),
|
||||
$this->container->get('config.manager')
|
||||
);
|
||||
$this->assertIdentical($storage_comparer->createChangelist()->getChangelist(), $storage_comparer->getEmptyChangelist());
|
||||
}
|
||||
|
|
|
@ -49,7 +49,8 @@ class ConfigImportRecreateTest extends DrupalUnitTestBase {
|
|||
// Set up the ConfigImporter object for testing.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.staging'),
|
||||
$this->container->get('config.storage')
|
||||
$this->container->get('config.storage'),
|
||||
$this->container->get('config.manager')
|
||||
);
|
||||
$this->configImporter = new ConfigImporter(
|
||||
$storage_comparer->createChangelist(),
|
||||
|
|
|
@ -56,7 +56,8 @@ class ConfigImportRenameValidationTest extends DrupalUnitTestBase {
|
|||
// Set up the ConfigImporter object for testing.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.staging'),
|
||||
$this->container->get('config.storage')
|
||||
$this->container->get('config.storage'),
|
||||
$this->container->get('config.manager')
|
||||
);
|
||||
$this->configImporter = new ConfigImporter(
|
||||
$storage_comparer->createChangelist(),
|
||||
|
|
|
@ -54,7 +54,8 @@ class ConfigImporterTest extends DrupalUnitTestBase {
|
|||
// Set up the ConfigImporter object for testing.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.staging'),
|
||||
$this->container->get('config.storage')
|
||||
$this->container->get('config.storage'),
|
||||
$this->container->get('config.manager')
|
||||
);
|
||||
$this->configImporter = new ConfigImporter(
|
||||
$storage_comparer->createChangelist(),
|
||||
|
|
|
@ -11,6 +11,8 @@ use Drupal\simpletest\DrupalUnitTestBase;
|
|||
|
||||
/**
|
||||
* Tests installation of configuration objects in installation functionality.
|
||||
*
|
||||
* @see \Drupal\Core\Config\ConfigInstaller
|
||||
*/
|
||||
class ConfigInstallTest extends DrupalUnitTestBase {
|
||||
public static function getInfo() {
|
||||
|
@ -78,6 +80,99 @@ class ConfigInstallTest extends DrupalUnitTestBase {
|
|||
\Drupal::config('core.extension')->set('module', array())->save();
|
||||
\Drupal::service('config.manager')->uninstall('module', 'config_test');
|
||||
$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.');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that collections are ignored if the event does not return anything.
|
||||
*/
|
||||
public function testCollectionInstallationNoCollections() {
|
||||
// Install the test module.
|
||||
$this->enableModules(array('config_collection_install_test'));
|
||||
$this->installConfig(array('config_collection_install_test'));
|
||||
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
|
||||
$active_storage = \Drupal::service('config.storage');
|
||||
$this->assertEqual(array(), $active_storage->getAllCollectionNames());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests config objects in collections are installed as expected.
|
||||
*/
|
||||
public function testCollectionInstallationCollections() {
|
||||
$collections = array(
|
||||
'another_collection',
|
||||
'collection.test1',
|
||||
'collection.test2',
|
||||
);
|
||||
// Set the event listener to return three possible collections.
|
||||
// @see \Drupal\config_collection_install_test\EventSubscriber
|
||||
\Drupal::state()->set('config_collection_install_test.collection_names', $collections);
|
||||
// Install the test module.
|
||||
$this->enableModules(array('config_collection_install_test'));
|
||||
$this->installConfig(array('config_collection_install_test'));
|
||||
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
|
||||
$active_storage = \Drupal::service('config.storage');
|
||||
$this->assertEqual($collections, $active_storage->getAllCollectionNames());
|
||||
foreach ($collections as $collection) {
|
||||
$collection_storage = $active_storage->createCollection($collection);
|
||||
$data = $collection_storage->read('config_collection_install_test.test');
|
||||
$this->assertEqual($collection, $data['collection']);
|
||||
}
|
||||
|
||||
// Test that the we can use the config installer to install all the
|
||||
// available default configuration in a particular collection for enabled
|
||||
// extensions.
|
||||
\Drupal::service('config.installer')->installCollectionDefaultConfig('entity');
|
||||
// The 'entity' collection will not exist because the 'config_test' module
|
||||
// is not enabled.
|
||||
$this->assertEqual($collections, $active_storage->getAllCollectionNames());
|
||||
// Enable the 'config_test' module and try again.
|
||||
$this->enableModules(array('config_test'));
|
||||
\Drupal::service('config.installer')->installCollectionDefaultConfig('entity');
|
||||
$collections[] = 'entity';
|
||||
$this->assertEqual($collections, $active_storage->getAllCollectionNames());
|
||||
$collection_storage = $active_storage->createCollection('entity');
|
||||
$data = $collection_storage->read('config_test.dynamic.dotted.default');
|
||||
$this->assertIdentical(array('label' => 'entity'), $data);
|
||||
|
||||
// Test that the config manager uninstalls configuration from collections
|
||||
// as expected.
|
||||
\Drupal::service('config.manager')->uninstall('module', 'config_collection_install_test');
|
||||
$this->assertEqual(array('entity'), $active_storage->getAllCollectionNames());
|
||||
\Drupal::service('config.manager')->uninstall('module', 'config_test');
|
||||
$this->assertEqual(array(), $active_storage->getAllCollectionNames());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests collections which do not support config entities install correctly.
|
||||
*
|
||||
* Config entity detection during config installation is done by matching
|
||||
* config name prefixes. If a collection provides a configuration with a
|
||||
* matching name but does not support config entities it should be created
|
||||
* using simple configuration.
|
||||
*/
|
||||
public function testCollectionInstallationCollectionConfigEntity() {
|
||||
$collections = array(
|
||||
'entity',
|
||||
);
|
||||
\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'));
|
||||
/** @var \Drupal\Core\Config\StorageInterface $active_storage */
|
||||
$active_storage = \Drupal::service('config.storage');
|
||||
$this->assertEqual($collections, $active_storage->getAllCollectionNames());
|
||||
$collection_storage = $active_storage->createCollection('entity');
|
||||
|
||||
// The config_test.dynamic.dotted.default configuraton object saved in the
|
||||
// active store should be a configuration entity complete with UUID. Because
|
||||
// the entity collection does not support configuration entities the
|
||||
// configuration object stored there with the same name should only contain
|
||||
// a label.
|
||||
$name = 'config_test.dynamic.dotted.default';
|
||||
$data = $active_storage->read($name);
|
||||
$this->assertTrue(isset($data['uuid']));
|
||||
$data = $collection_storage->read($name);
|
||||
$this->assertIdentical(array('label' => 'entity'), $data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,12 +45,13 @@ class ConfigSnapshotTest extends DrupalUnitTestBase {
|
|||
$active = $this->container->get('config.storage');
|
||||
$staging = $this->container->get('config.storage.staging');
|
||||
$snapshot = $this->container->get('config.storage.snapshot');
|
||||
$config_manager = $this->container->get('config.manager');
|
||||
$config_name = 'config_test.system';
|
||||
$config_key = 'foo';
|
||||
$new_data = 'foobar';
|
||||
|
||||
$active_snapshot_comparer = new StorageComparer($active, $snapshot);
|
||||
$staging_snapshot_comparer = new StorageComparer($staging, $snapshot);
|
||||
$active_snapshot_comparer = new StorageComparer($active, $snapshot, $config_manager);
|
||||
$staging_snapshot_comparer = new StorageComparer($staging, $snapshot, $config_manager);
|
||||
|
||||
// Verify that we have an initial snapshot that matches the active
|
||||
// configuration. This has to be true as no config should be installed.
|
||||
|
|
|
@ -42,8 +42,8 @@ class CachedStorageTest extends ConfigStorageTestBase {
|
|||
function setUp() {
|
||||
parent::setUp();
|
||||
$this->filestorage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
|
||||
$this->storage = new CachedStorage($this->filestorage, \Drupal::service('cache_factory'));
|
||||
$this->cache = \Drupal::service('cache_factory')->get('config');
|
||||
$this->storage = new CachedStorage($this->filestorage, $this->cache);
|
||||
// ::listAll() verifications require other configuration data to exist.
|
||||
$this->storage->write('system.performance', array());
|
||||
}
|
||||
|
|
|
@ -181,6 +181,80 @@ abstract class ConfigStorageTestBase extends DrupalUnitTestBase {
|
|||
$this->assertIdentical($read_data, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the storage supports collections.
|
||||
*/
|
||||
public function testCollection() {
|
||||
$name = 'config_test.storage';
|
||||
$data = array('foo' => 'bar');
|
||||
$result = $this->storage->write($name, $data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertIdentical($data, $this->storage->read($name));
|
||||
|
||||
// Create configuration in a new collection.
|
||||
$new_storage = $this->storage->createCollection('collection.sub.new');
|
||||
$this->assertFalse($new_storage->exists($name));
|
||||
$this->assertEqual(array(), $new_storage->listAll());
|
||||
$new_storage->write($name, $data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertIdentical($data, $new_storage->read($name));
|
||||
$this->assertEqual(array($name), $new_storage->listAll());
|
||||
$this->assertTrue($new_storage->exists($name));
|
||||
$new_data = array('foo' => 'baz');
|
||||
$new_storage->write($name, $new_data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertIdentical($new_data, $new_storage->read($name));
|
||||
|
||||
// Create configuration in another collection.
|
||||
$another_storage = $this->storage->createCollection('collection.sub.another');
|
||||
$this->assertFalse($another_storage->exists($name));
|
||||
$this->assertEqual(array(), $another_storage->listAll());
|
||||
$another_storage->write($name, $new_data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertIdentical($new_data, $another_storage->read($name));
|
||||
$this->assertEqual(array($name), $another_storage->listAll());
|
||||
$this->assertTrue($another_storage->exists($name));
|
||||
|
||||
// Create configuration in yet another collection.
|
||||
$alt_storage = $this->storage->createCollection('alternate');
|
||||
$alt_storage->write($name, $new_data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertIdentical($new_data, $alt_storage->read($name));
|
||||
|
||||
// Switch back to the collection-less mode and check the data still exists
|
||||
// add has not been touched.
|
||||
$this->assertIdentical($data, $this->storage->read($name));
|
||||
|
||||
// Check that the getAllCollectionNames() method works.
|
||||
$this->assertIdentical(array('alternate', 'collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
|
||||
|
||||
// Check that the collections are removed when they are empty.
|
||||
$alt_storage->delete($name);
|
||||
$this->assertIdentical(array('collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
|
||||
|
||||
// Create configuration in collection called 'collection'. This ensures that
|
||||
// FileStorage's collection storage works regardless of its use of
|
||||
// subdirectories.
|
||||
$parent_storage = $this->storage->createCollection('collection');
|
||||
$this->assertFalse($parent_storage->exists($name));
|
||||
$this->assertEqual(array(), $parent_storage->listAll());
|
||||
$parent_storage->write($name, $new_data);
|
||||
$this->assertIdentical($result, TRUE);
|
||||
$this->assertIdentical($new_data, $parent_storage->read($name));
|
||||
$this->assertEqual(array($name), $parent_storage->listAll());
|
||||
$this->assertTrue($parent_storage->exists($name));
|
||||
$this->assertIdentical(array('collection', 'collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
|
||||
$parent_storage->deleteAll();
|
||||
$this->assertIdentical(array('collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
|
||||
|
||||
// Check that the having an empty collection-less storage does not break
|
||||
// anything. Before deleting check that the previous delete did not affect
|
||||
// data in another collection.
|
||||
$this->assertIdentical($data, $this->storage->read($name));
|
||||
$this->storage->delete($name);
|
||||
$this->assertIdentical(array('collection.sub.another', 'collection.sub.new'), $this->storage->getAllCollectionNames());
|
||||
}
|
||||
|
||||
abstract protected function read($name);
|
||||
|
||||
abstract protected function insert($name, $data);
|
||||
|
@ -188,4 +262,5 @@ abstract class ConfigStorageTestBase extends DrupalUnitTestBase {
|
|||
abstract protected function update($name, $data);
|
||||
|
||||
abstract protected function delete($name);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
collection: another_collection
|
|
@ -0,0 +1 @@
|
|||
collection: collection.test1
|
|
@ -0,0 +1 @@
|
|||
collection: collection.test2
|
|
@ -0,0 +1 @@
|
|||
label: entity
|
|
@ -0,0 +1,5 @@
|
|||
name: 'Configuration events test'
|
||||
type: module
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,6 @@
|
|||
services:
|
||||
config_events_test.event_subscriber:
|
||||
class: Drupal\config_collection_install_test\EventSubscriber
|
||||
arguments: ['@state']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\config_collection_install_test\EventSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\config_collection_install_test;
|
||||
|
||||
use Drupal\Core\Config\ConfigCollectionNamesEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class EventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* The state key value store.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Constructs the Event Subscriber object.
|
||||
*
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state key value store.
|
||||
*/
|
||||
public function __construct(StateInterface $state) {
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reacts to the ConfigEvents::COLLECTION_NAMES event.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigCollectionNamesEvent $event
|
||||
* The configuration collection names event.
|
||||
*/
|
||||
public function addCollectionNames(ConfigCollectionNamesEvent $event) {
|
||||
$event->addCollectionNames($this->state->get('config_collection_install_test.collection_names', array()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[ConfigEvents::COLLECTION_NAMES][] = array('addCollectionNames');
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
|
@ -1481,7 +1481,8 @@ abstract class TestBase {
|
|||
// Set up the ConfigImporter object for testing.
|
||||
$storage_comparer = new StorageComparer(
|
||||
$this->container->get('config.storage.staging'),
|
||||
$this->container->get('config.storage')
|
||||
$this->container->get('config.storage'),
|
||||
$this->container->get('config.manager')
|
||||
);
|
||||
$this->configImporter = new ConfigImporter(
|
||||
$storage_comparer,
|
||||
|
|
|
@ -6,7 +6,6 @@ use Drupal\Tests\UnitTestCase;
|
|||
use Drupal\Core\Config\CachedStorage;
|
||||
use Drupal\Core\Cache\MemoryBackend;
|
||||
use Drupal\Core\Cache\NullBackend;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
|
||||
/**
|
||||
* Tests the interaction of cache and file storage in CachedStorage.
|
||||
|
@ -15,6 +14,11 @@ use Drupal\Core\Cache\CacheBackendInterface;
|
|||
*/
|
||||
class CachedStorageTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Cache\CacheFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $cacheFactory;
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Config cached storage test',
|
||||
|
@ -23,6 +27,10 @@ class CachedStorageTest extends UnitTestCase {
|
|||
);
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
$this->cacheFactory = $this->getMock('Drupal\Core\Cache\CacheFactoryInterface');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test listAll static cache.
|
||||
*/
|
||||
|
@ -37,7 +45,11 @@ class CachedStorageTest extends UnitTestCase {
|
|||
->will($this->returnValue($response));
|
||||
|
||||
$cache = new NullBackend(__FUNCTION__);
|
||||
$cachedStorage = new CachedStorage($storage, $cache);
|
||||
$this->cacheFactory->expects($this->once())
|
||||
->method('get')
|
||||
->with('config')
|
||||
->will($this->returnValue($cache));
|
||||
$cachedStorage = new CachedStorage($storage, $this->cacheFactory);
|
||||
$this->assertEquals($response, $cachedStorage->listAll($prefix));
|
||||
$this->assertEquals($response, $cachedStorage->listAll($prefix));
|
||||
}
|
||||
|
@ -53,7 +65,11 @@ class CachedStorageTest extends UnitTestCase {
|
|||
$response = array("$prefix." . $this->randomName(), "$prefix." . $this->randomName());
|
||||
$cache = new MemoryBackend(__FUNCTION__);
|
||||
$cache->set('find:' . $prefix, $response);
|
||||
$cachedStorage = new CachedStorage($storage, $cache);
|
||||
$this->cacheFactory->expects($this->once())
|
||||
->method('get')
|
||||
->with('config')
|
||||
->will($this->returnValue($cache));
|
||||
$cachedStorage = new CachedStorage($storage, $this->cacheFactory);
|
||||
$this->assertEquals($response, $cachedStorage->listAll($prefix));
|
||||
}
|
||||
|
||||
|
@ -79,7 +95,11 @@ class CachedStorageTest extends UnitTestCase {
|
|||
foreach ($configCacheValues as $key => $value) {
|
||||
$cache->set($key, $value);
|
||||
}
|
||||
$cachedStorage = new CachedStorage($storage, $cache);
|
||||
$this->cacheFactory->expects($this->once())
|
||||
->method('get')
|
||||
->with('config')
|
||||
->will($this->returnValue($cache));
|
||||
$cachedStorage = new CachedStorage($storage, $this->cacheFactory);
|
||||
$this->assertEquals($configCacheValues, $cachedStorage->readMultiple($configNames));
|
||||
}
|
||||
|
||||
|
@ -119,7 +139,11 @@ class CachedStorageTest extends UnitTestCase {
|
|||
->with(array(2 => $configNames[2], 4 => $configNames[4]))
|
||||
->will($this->returnValue($response));
|
||||
|
||||
$cachedStorage = new CachedStorage($storage, $cache);
|
||||
$this->cacheFactory->expects($this->once())
|
||||
->method('get')
|
||||
->with('config')
|
||||
->will($this->returnValue($cache));
|
||||
$cachedStorage = new CachedStorage($storage, $this->cacheFactory);
|
||||
$expected_data = $configCacheValues + array($configNames[2] => $config_exists_not_cached_data);
|
||||
$this->assertEquals($expected_data, $cachedStorage->readMultiple($configNames));
|
||||
|
||||
|
@ -143,7 +167,11 @@ class CachedStorageTest extends UnitTestCase {
|
|||
->method('read')
|
||||
->with($name)
|
||||
->will($this->returnValue(FALSE));
|
||||
$cachedStorage = new CachedStorage($storage, $cache);
|
||||
$this->cacheFactory->expects($this->once())
|
||||
->method('get')
|
||||
->with('config')
|
||||
->will($this->returnValue($cache));
|
||||
$cachedStorage = new CachedStorage($storage, $this->cacheFactory);
|
||||
|
||||
$this->assertFalse($cachedStorage->read($name));
|
||||
|
||||
|
@ -163,7 +191,11 @@ class CachedStorageTest extends UnitTestCase {
|
|||
$storage = $this->getMock('Drupal\Core\Config\StorageInterface');
|
||||
$storage->expects($this->never())
|
||||
->method('read');
|
||||
$cachedStorage = new CachedStorage($storage, $cache);
|
||||
$this->cacheFactory->expects($this->once())
|
||||
->method('get')
|
||||
->with('config')
|
||||
->will($this->returnValue($cache));
|
||||
$cachedStorage = new CachedStorage($storage, $this->cacheFactory);
|
||||
$this->assertFalse($cachedStorage->read($name));
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,11 @@ class StorageComparerTest extends UnitTestCase {
|
|||
*/
|
||||
protected $targetStorage;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Config\ConfigManager|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $configManager;
|
||||
|
||||
/**
|
||||
* The storage comparer to test.
|
||||
*
|
||||
|
@ -54,7 +59,8 @@ class StorageComparerTest extends UnitTestCase {
|
|||
public function setUp() {
|
||||
$this->sourceStorage = $this->getMock('Drupal\Core\Config\StorageInterface');
|
||||
$this->targetStorage = $this->getMock('Drupal\Core\Config\StorageInterface');
|
||||
$this->storageComparer = new StorageComparer($this->sourceStorage, $this->targetStorage);
|
||||
$this->configManager = $this->getMock('Drupal\Core\Config\ConfigManagerInterface');
|
||||
$this->storageComparer = new StorageComparer($this->sourceStorage, $this->targetStorage, $this->configManager);
|
||||
}
|
||||
|
||||
protected function getConfigData() {
|
||||
|
@ -123,6 +129,15 @@ class StorageComparerTest extends UnitTestCase {
|
|||
$this->targetStorage->expects($this->once())
|
||||
->method('readMultiple')
|
||||
->will($this->returnValue($config_data));
|
||||
$this->sourceStorage->expects($this->once())
|
||||
->method('getAllCollectionNames')
|
||||
->will($this->returnValue(array()));
|
||||
$this->targetStorage->expects($this->once())
|
||||
->method('getAllCollectionNames')
|
||||
->will($this->returnValue(array()));
|
||||
$this->configManager->expects($this->any())
|
||||
->method('supportsConfigurationEntities')
|
||||
->will($this->returnValue(TRUE));
|
||||
|
||||
$this->storageComparer->createChangelist();
|
||||
$this->assertEmpty($this->storageComparer->getChangelist('create'));
|
||||
|
@ -151,6 +166,15 @@ class StorageComparerTest extends UnitTestCase {
|
|||
$this->targetStorage->expects($this->once())
|
||||
->method('readMultiple')
|
||||
->will($this->returnValue($target_data));
|
||||
$this->sourceStorage->expects($this->once())
|
||||
->method('getAllCollectionNames')
|
||||
->will($this->returnValue(array()));
|
||||
$this->targetStorage->expects($this->once())
|
||||
->method('getAllCollectionNames')
|
||||
->will($this->returnValue(array()));
|
||||
$this->configManager->expects($this->any())
|
||||
->method('supportsConfigurationEntities')
|
||||
->will($this->returnValue(TRUE));
|
||||
|
||||
$this->storageComparer->createChangelist();
|
||||
$expected = array(
|
||||
|
@ -184,6 +208,15 @@ class StorageComparerTest extends UnitTestCase {
|
|||
$this->targetStorage->expects($this->once())
|
||||
->method('readMultiple')
|
||||
->will($this->returnValue($target_data));
|
||||
$this->sourceStorage->expects($this->once())
|
||||
->method('getAllCollectionNames')
|
||||
->will($this->returnValue(array()));
|
||||
$this->targetStorage->expects($this->once())
|
||||
->method('getAllCollectionNames')
|
||||
->will($this->returnValue(array()));
|
||||
$this->configManager->expects($this->any())
|
||||
->method('supportsConfigurationEntities')
|
||||
->will($this->returnValue(TRUE));
|
||||
|
||||
$this->storageComparer->createChangelist();
|
||||
$expected = array(
|
||||
|
@ -217,6 +250,15 @@ class StorageComparerTest extends UnitTestCase {
|
|||
$this->targetStorage->expects($this->once())
|
||||
->method('readMultiple')
|
||||
->will($this->returnValue($target_data));
|
||||
$this->sourceStorage->expects($this->once())
|
||||
->method('getAllCollectionNames')
|
||||
->will($this->returnValue(array()));
|
||||
$this->targetStorage->expects($this->once())
|
||||
->method('getAllCollectionNames')
|
||||
->will($this->returnValue(array()));
|
||||
$this->configManager->expects($this->any())
|
||||
->method('supportsConfigurationEntities')
|
||||
->will($this->returnValue(TRUE));
|
||||
|
||||
$this->storageComparer->createChangelist();
|
||||
$expected = array(
|
||||
|
|
Loading…
Reference in New Issue