diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php index e4a7d648b31925bb21e2da4ea2b6903321870abf..d5b0a0200ba48b6f36b97cad5a7b715635066f9b 100644 --- a/core/lib/Drupal/Core/Config/ConfigManager.php +++ b/core/lib/Drupal/Core/Config/ConfigManager.php @@ -22,6 +22,7 @@ class ConfigManager implements ConfigManagerInterface { use StringTranslationTrait; use DeprecatedServicePropertyTrait; + use StorageCopyTrait; /** * {@inheritdoc} @@ -207,23 +208,7 @@ public function diff(StorageInterface $source_storage, StorageInterface $target_ * {@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 0000000000000000000000000000000000000000..9cd80199a0886dacb0a057b5f5264f3880845e52 --- /dev/null +++ b/core/lib/Drupal/Core/Config/StorageCopyTrait.php @@ -0,0 +1,41 @@ +<?php + +namespace Drupal\Core\Config; + +/** + * Utility trait to copy configuration from one storage to another. + */ +trait StorageCopyTrait { + + /** + * Copy the configuration from one storage to another and remove stale items. + * + * This method empties target storage and copies all collections from source. + * Configuration is only copied and not imported, should not be used + * with the active storage as the target. + * + * @param \Drupal\Core\Config\StorageInterface $source + * The configuration storage to copy from. + * @param \Drupal\Core\Config\StorageInterface $target + * The configuration storage to copy to. + */ + protected static function replaceStorageContents(StorageInterface $source, StorageInterface &$target) { + // Make sure there is no stale configuration in the target storage. + foreach (array_merge([StorageInterface::DEFAULT_COLLECTION], $target->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 5bab85949fed9fbac5121c666dd85dee16f32b91..45a7c728bf509f1e71e07a9e9196ae6d2cfb2c6d 100644 --- a/core/tests/Drupal/Tests/ConfigTestTrait.php +++ b/core/tests/Drupal/Tests/ConfigTestTrait.php @@ -4,6 +4,7 @@ use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Config\StorageComparer; +use Drupal\Core\Config\StorageCopyTrait; use Drupal\Core\Config\StorageInterface; /** @@ -11,6 +12,8 @@ */ trait ConfigTestTrait { + use StorageCopyTrait; + /** * Returns a ConfigImporter object to import test configuration. * @@ -49,10 +52,7 @@ protected function configImporter() { * 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 0000000000000000000000000000000000000000..eb00129a20b4efe0416623f2ad220b2d54deaabe --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Config/StorageCopyTraitTest.php @@ -0,0 +1,114 @@ +<?php + +namespace Drupal\Tests\Core\Config; + +use Drupal\Core\Config\MemoryStorage; +use Drupal\Core\Config\StorageCopyTrait; +use Drupal\Core\Config\StorageInterface; +use Drupal\Tests\UnitTestCase; + +/** + * @coversDefaultClass \Drupal\Core\Config\StorageCopyTrait + * @group Config + */ +class StorageCopyTraitTest extends UnitTestCase { + + use StorageCopyTrait; + + /** + * @covers ::replaceStorageContents + * + * @dataProvider providerTestReplaceStorageContents + */ + public function testReplaceStorageContents($source_collections, $target_collections) { + $source = new MemoryStorage(); + $target = new MemoryStorage(); + // Empty the storage should be the same. + $this->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()); + } + } + } + } + +}