sourceCacheStorage = new MemoryBackend(); $this->sourceStorage = new CachedStorage( $source_storage, $this->sourceCacheStorage ); $this->targetCacheStorage = new MemoryBackend(); $this->targetStorage = new CachedStorage( $target_storage, $this->targetCacheStorage ); $this->configManager = $config_manager; $this->changelist[StorageInterface::DEFAULT_COLLECTION] = $this->getEmptyChangelist(); } /** * {@inheritdoc} */ 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($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]; } /** * {@inheritdoc} */ public function getEmptyChangelist() { return [ 'create' => [], 'update' => [], 'delete' => [], 'rename' => [], ]; } /** * {@inheritdoc} */ public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) { if ($op) { return $this->changelist[$collection][$op]; } 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 * Array of changes to add to the changelist. * @param array $sort_order * 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($collection, $op, array $changes, array $sort_order = NULL) { // Only add changes that aren't already listed. $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[$collection][$op]); // Sort the changelist in the same order as the $sort_order array and // ensure the array is keyed from 0. $this->changelist[$collection][$op] = array_values(array_intersect($sort_order, $this->changelist[$collection][$op])); if ($count != count($this->changelist[$collection][$op])) { throw new \InvalidArgumentException("Sorting the $op changelist should not change its length."); } } } /** * {@inheritdoc} */ public function createChangelist() { 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 ($collection == StorageInterface::DEFAULT_COLLECTION) { $this->addChangelistRename($collection); } } return $this; } /** * Creates the delete changelist. * * The list of deletes is sorted so that dependencies are deleted after * configuration entities that depend on them. For example, fields should be * deleted after field storages. * * @param string $collection * The storage collection to operate on. */ protected function addChangelistDelete($collection) { $deletes = array_diff(array_reverse($this->targetNames[$collection]), $this->sourceNames[$collection]); $this->addChangeList($collection, 'delete', $deletes); } /** * Creates the create changelist. * * The list of creates is sorted so that dependencies are created before * configuration entities that depend on them. For example, field storages * should be created before fields. * * @param string $collection * The storage collection to operate on. */ protected function addChangelistCreate($collection) { $creates = array_diff($this->sourceNames[$collection], $this->targetNames[$collection]); $this->addChangeList($collection, 'create', $creates); } /** * Creates the update changelist. * * The list of updates is sorted so that dependencies are created before * configuration entities that depend on them. For example, field storages * should be updated before fields. * * @param string $collection * The storage collection to operate on. */ protected function addChangelistUpdate($collection) { $recreates = []; foreach (array_intersect($this->sourceNames[$collection], $this->targetNames[$collection]) as $name) { $source_data = $this->getSourceStorage($collection)->read($name); $target_data = $this->getTargetStorage($collection)->read($name); if ($source_data !== $target_data) { if (isset($source_data['uuid']) && $source_data['uuid'] !== $target_data['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 // synchronization should do the same. $recreates[] = $name; } else { $this->addChangeList($collection, 'update', [$name]); } } } if (!empty($recreates)) { // Recreates should become deletes and creates. Deletes should be ordered // so that dependencies are deleted first. $this->addChangeList($collection, 'create', $recreates, $this->sourceNames[$collection]); $this->addChangeList($collection, 'delete', $recreates, array_reverse($this->targetNames[$collection])); } } /** * Creates the rename changelist. * * 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($collection) { // Renames will be present in both the create and delete lists. $create_list = $this->getChangelist('create', $collection); $delete_list = $this->getChangelist('delete', $collection); if (empty($create_list) || empty($delete_list)) { return; } $create_uuids = []; foreach ($this->sourceNames[$collection] as $name) { $data = $this->getSourceStorage($collection)->read($name); if (isset($data['uuid']) && in_array($name, $create_list)) { $create_uuids[$data['uuid']] = $name; } } if (empty($create_uuids)) { return; } $renames = []; // Renames should be ordered so that dependencies are renamed last. This // ensures that if there is logic in the configuration entity class to keep // names in sync it will still work. $this->targetNames is in the desired // order due to the use of configuration dependencies in // \Drupal\Core\Config\StorageComparer::getAndSortConfigData(). // 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[$collection] as $name) { $data = $this->getTargetStorage($collection)->read($name); if (isset($data['uuid']) && isset($create_uuids[$data['uuid']])) { // Remove the item from the create list. $this->removeFromChangelist($collection, 'create', $create_uuids[$data['uuid']]); // Remove the item from the delete list. $this->removeFromChangelist($collection, 'delete', $name); // Create the rename name. $renames[] = $this->createRenameName($name, $create_uuids[$data['uuid']]); } } $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($collection, $op, $name) { $key = array_search($name, $this->changelist[$collection][$op]); if ($key !== FALSE) { unset($this->changelist[$collection][$op][$key]); } } /** * {@inheritdoc} */ public function moveRenameToUpdate($rename, $collection = StorageInterface::DEFAULT_COLLECTION) { $names = $this->extractRenameNames($rename); $this->removeFromChangelist($collection, 'rename', $rename); $this->addChangeList($collection, 'update', [$names['new_name']], $this->sourceNames[$collection]); } /** * {@inheritdoc} */ public function reset() { $this->changelist = [StorageInterface::DEFAULT_COLLECTION => $this->getEmptyChangelist()]; $this->sourceNames = $this->targetNames = []; // Reset the static configuration data caches. $this->sourceCacheStorage->deleteAll(); $this->targetCacheStorage->deleteAll(); return $this->createChangelist(); } /** * {@inheritdoc} */ public function hasChanges() { foreach ($this->getAllCollectionNames() as $collection) { foreach (['delete', 'create', 'update', 'rename'] as $op) { if (!empty($this->changelist[$collection][$op])) { return TRUE; } } } return FALSE; } /** * {@inheritdoc} */ public function validateSiteUuid() { $source = $this->sourceStorage->read('system.site'); $target = $this->targetStorage->read('system.site'); return $source['uuid'] === $target['uuid']; } /** * Gets and sorts configuration data from the source and target storages. */ protected function getAndSortConfigData($collection) { $source_storage = $this->getSourceStorage($collection); $target_storage = $this->getTargetStorage($collection); $target_names = $target_storage->listAll(); $source_names = $source_storage->listAll(); // Prime the static caches by reading all the configuration in the source // and target storages. $target_data = $target_storage->readMultiple($target_names); $source_data = $source_storage->readMultiple($source_names); // If the collection only supports simple configuration do not use // configuration dependencies. if ($collection == StorageInterface::DEFAULT_COLLECTION) { $dependency_manager = new ConfigDependencyManager(); $this->targetNames[$collection] = $dependency_manager->setData($target_data)->sortAll(); $this->sourceNames[$collection] = $dependency_manager->setData($source_data)->sortAll(); } else { $this->targetNames[$collection] = $target_names; $this->sourceNames[$collection] = $source_names; } } /** * Creates a rename name from the old and new names for the object. * * @param string $old_name * The old configuration object name. * @param string $new_name * The new configuration object name. * * @return string * The configuration change name that encodes both the old and the new name. * * @see \Drupal\Core\Config\StorageComparerInterface::extractRenameNames() */ protected function createRenameName($old_name, $new_name) { return $old_name . '::' . $new_name; } /** * {@inheritdoc} */ public function extractRenameNames($name) { $names = explode('::', $name, 2); return [ 'old_name' => $names[0], '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; } }