diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php index e4a7d648b31..d5b0a0200ba 100644 --- a/core/lib/Drupal/Core/Config/ConfigManager.php +++ b/core/lib/Drupal/Core/Config/ConfigManager.php @@ -22,6 +22,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; class ConfigManager implements ConfigManagerInterface { use StringTranslationTrait; use DeprecatedServicePropertyTrait; + use StorageCopyTrait; /** * {@inheritdoc} @@ -207,23 +208,7 @@ 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)); - } - } + self::replaceStorageContents($source_storage, $snapshot_storage); } /** diff --git a/core/lib/Drupal/Core/Config/StorageCopyTrait.php b/core/lib/Drupal/Core/Config/StorageCopyTrait.php new file mode 100644 index 00000000000..9cd80199a08 --- /dev/null +++ b/core/lib/Drupal/Core/Config/StorageCopyTrait.php @@ -0,0 +1,41 @@ +getAllCollectionNames()) as $collection) { + $target->createCollection($collection)->deleteAll(); + } + + // Copy all the configuration from all the collections. + foreach (array_merge([StorageInterface::DEFAULT_COLLECTION], $source->getAllCollectionNames()) as $collection) { + $source_collection = $source->createCollection($collection); + $target_collection = $target->createCollection($collection); + foreach ($source_collection->listAll() as $name) { + $target_collection->write($name, $source_collection->read($name)); + } + } + + // Make sure that the target is set to the same collection as the source. + $target = $target->createCollection($source->getCollectionName()); + } + +} diff --git a/core/tests/Drupal/Tests/ConfigTestTrait.php b/core/tests/Drupal/Tests/ConfigTestTrait.php index 5bab85949fe..45a7c728bf5 100644 --- a/core/tests/Drupal/Tests/ConfigTestTrait.php +++ b/core/tests/Drupal/Tests/ConfigTestTrait.php @@ -4,6 +4,7 @@ namespace Drupal\Tests; use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Config\StorageComparer; +use Drupal\Core\Config\StorageCopyTrait; use Drupal\Core\Config\StorageInterface; /** @@ -11,6 +12,8 @@ use Drupal\Core\Config\StorageInterface; */ trait ConfigTestTrait { + use StorageCopyTrait; + /** * Returns a ConfigImporter object to import test configuration. * @@ -49,10 +52,7 @@ trait ConfigTestTrait { * The target config storage service. */ protected function copyConfig(StorageInterface $source_storage, StorageInterface $target_storage) { - $target_storage->deleteAll(); - foreach ($source_storage->listAll() as $name) { - $target_storage->write($name, $source_storage->read($name)); - } + static::replaceStorageContents($source_storage, $target_storage); } } diff --git a/core/tests/Drupal/Tests/Core/Config/StorageCopyTraitTest.php b/core/tests/Drupal/Tests/Core/Config/StorageCopyTraitTest.php new file mode 100644 index 00000000000..eb00129a20b --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Config/StorageCopyTraitTest.php @@ -0,0 +1,114 @@ +assertArrayEquals(self::toArray($source), self::toArray($target)); + + // When the source is populated, they are not the same any more. + $this->generateRandomData($source, $source_collections); + $this->assertNotEquals(self::toArray($source), self::toArray($target)); + + // When the target is filled with random data they are also not the same. + $this->generateRandomData($target, $target_collections); + $this->assertNotEquals(self::toArray($source), self::toArray($target)); + + // Set the active collection to a random one on both source and target. + if ($source_collections) { + $collections = $source->getAllCollectionNames(); + $source = $source->createCollection($collections[array_rand($collections)]); + } + if ($target_collections) { + $collections = $target->getAllCollectionNames(); + $target = $target->createCollection($collections[array_rand($collections)]); + } + + $source_data = self::toArray($source); + $source_name = $source->getCollectionName(); + + // After copying they are the same, this asserts that items not present + // in the source get removed from the target. + self::replaceStorageContents($source, $target); + $this->assertArrayEquals($source_data, self::toArray($target)); + // Assert that the copy method did indeed not change the source. + $this->assertArrayEquals($source_data, self::toArray($source)); + + // Assert that the active collection is the same as the original source. + $this->assertEquals($source_name, $source->getCollectionName()); + $this->assertEquals($source_name, $target->getCollectionName()); + } + + /** + * Provides data for testCheckRequirements(). + */ + public function providerTestReplaceStorageContents() { + $data = []; + $data[] = [TRUE, TRUE]; + $data[] = [TRUE, FALSE]; + $data[] = [FALSE, TRUE]; + $data[] = [FALSE, FALSE]; + + return $data; + } + + /** + * Get the protected config data out of a MemoryStorage. + * + * @param \Drupal\Core\Config\MemoryStorage $storage + * The config storage to extract the data from. + * + * @return array + */ + protected static function toArray(MemoryStorage $storage) { + $reflection = new \ReflectionObject($storage); + $property = $reflection->getProperty('config'); + $property->setAccessible(TRUE); + + return $property->getValue($storage)->getArrayCopy(); + } + + /** + * Generate random data in a config storage. + * + * @param \Drupal\Core\Config\StorageInterface $storage + * The storage to populate with random data. + * @param bool $collections + * Add random collections or not. + */ + protected function generateRandomData(StorageInterface $storage, $collections = TRUE) { + $generator = $this->getRandomGenerator(); + for ($i = 0; $i < rand(2, 10); $i++) { + $storage->write($this->randomMachineName(), (array) $generator->object()); + } + if ($collections) { + for ($i = 0; $i < rand(1, 5); $i++) { + $collection = $storage->createCollection($this->randomMachineName()); + for ($i = 0; $i < rand(2, 10); $i++) { + $collection->write($this->randomMachineName(), (array) $generator->object()); + } + } + } + } + +}