diff --git a/core/lib/Drupal/Core/Config/StorageComparer.php b/core/lib/Drupal/Core/Config/StorageComparer.php index c08cb5a..79e9d6e 100644 --- a/core/lib/Drupal/Core/Config/StorageComparer.php +++ b/core/lib/Drupal/Core/Config/StorageComparer.php @@ -6,6 +6,7 @@ */ namespace Drupal\Core\Config; +use Drupal\Core\Config\Entity\ConfigDependencyManager; /** * Defines a config storage comparer. @@ -36,8 +37,6 @@ class StorageComparer implements StorageComparerInterface { /** * Lists all the configuration object names in the source storage. * - * @see \Drupal\Core\Config\StorageComparer::getSourceNames() - * * @var array */ protected $sourceNames = array(); @@ -45,13 +44,25 @@ class StorageComparer implements StorageComparerInterface { /** * Lists all the configuration object names in the target storage. * - * @see \Drupal\Core\Config\StorageComparer::getTargetNames() - * * @var array */ protected $targetNames = array(); /** + * The source configuration data keyed by name. + * + * @var array + */ + protected $sourceData = array(); + + /** + * The target configuration data keyed by name. + * + * @var array + */ + protected $targetData = array(); + + /** * Constructs the Configuration storage comparer. * * @param \Drupal\Core\Config\StorageInterface $source_storage @@ -101,51 +112,69 @@ public function getChangelist($op = NULL) { } /** - * {@inheritdoc} + * Adds changes to the changelist. + * + * @param string $op + * The change operation performed. Either delete, create or update. + * @param array $changes + * Array of changes to add the changelist. */ - public function addChangeList($op, array $changes) { + protected function addChangeList($op, array $changes) { // Only add changes that aren't already listed. $changes = array_diff($changes, $this->changelist[$op]); $this->changelist[$op] = array_merge($this->changelist[$op], $changes); - return $this; } /** * {@inheritdoc} */ public function createChangelist() { - return $this - ->addChangelistCreate() - ->addChangelistUpdate() - ->addChangelistDelete(); + $this->setUpData(); + $this->addChangelistCreate(); + $this->addChangelistUpdate(); + $this->addChangelistDelete(); + $this->sourceData = NULL; + $this->targetData = NULL; + return $this; } /** - * {@inheritdoc} + * Creates the delete changelist. */ - public function addChangelistDelete() { - return $this->addChangeList('delete', array_diff($this->getTargetNames(), $this->getSourceNames())); + protected function addChangelistDelete() { + // Sort deletes in such a way that dependencies are deleted after + // configuration entities that depend on them. For example, field instances + // should be deleted before fields and fields should be deleted before + // content types. + $deletes = array_diff(array_reverse($this->targetNames), $this->sourceNames); + $this->addChangeList('delete', $deletes); } /** - * {@inheritdoc} + * Creates the create changelist. */ - public function addChangelistCreate() { - return $this->addChangeList('create', array_diff($this->getSourceNames(), $this->getTargetNames())); + protected function addChangelistCreate() { + // Organise creates in such a way that dependencies are created before + // configuration entities that depend on them. For example, field instances + // should be created after fields and fields should be created after + // content types. + $creates = array_diff($this->sourceNames, $this->targetNames); + $this->addChangeList('create', $creates); } /** - * {@inheritdoc} + * Creates the update changelist. */ - public function addChangelistUpdate() { - foreach (array_intersect($this->getSourceNames(), $this->getTargetNames()) as $name) { - $source_config_data = $this->sourceStorage->read($name); - $target_config_data = $this->targetStorage->read($name); - if ($source_config_data !== $target_config_data) { + protected function addChangelistUpdate() { + // Organise updates in such a way that dependencies are updated before + // configuration entities that depend on them. For example, field instances + // should be updated after fields and fields should be updated after + // content types. + foreach (array_intersect($this->sourceNames, $this->targetNames) as $name) { + if ($this->sourceData[$name] !== $this->targetData[$name]) { $this->addChangeList('update', array($name)); } } - return $this; } /** @@ -170,32 +199,6 @@ public function hasChanges($ops = array('delete', 'create', 'update')) { } /** - * Gets all the configuration names in the source storage. - * - * @return array - * List of all the configuration names in the source storage. - */ - protected function getSourceNames() { - if (empty($this->sourceNames)) { - $this->sourceNames = $this->sourceStorage->listAll(); - } - return $this->sourceNames; - } - - /** - * Gets all the configuration names in the target storage. - * - * @return array - * List of all the configuration names in the target storage. - */ - protected function getTargetNames() { - if (empty($this->targetNames)) { - $this->targetNames = $this->targetStorage->listAll(); - } - return $this->targetNames; - } - - /** * {@inheritdoc} */ public function validateSiteUuid() { @@ -204,4 +207,15 @@ public function validateSiteUuid() { return $source['uuid'] === $target['uuid']; } + /** + * Sets up the data required to determine and sort the change lists. + */ + protected function setUpData() { + $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(); + } + } diff --git a/core/lib/Drupal/Core/Config/StorageComparerInterface.php b/core/lib/Drupal/Core/Config/StorageComparerInterface.php index 0f62d0c..3c6c666 100644 --- a/core/lib/Drupal/Core/Config/StorageComparerInterface.php +++ b/core/lib/Drupal/Core/Config/StorageComparerInterface.php @@ -49,51 +49,6 @@ public function getEmptyChangelist(); public function getChangelist($op = NULL); /** - * Adds changes to the changelist. - * - * @param string $op - * The change operation performed. Either delete, create or update. - * @param array $changes - * Array of changes to add the changelist. - * - * @return \Drupal\Core\Config\StorageComparerInterface - * An object which implements the StorageComparerInterface. - */ - public function addChangeList($op, array $changes); - - /** - * Add differences between source and target configuration storage to changelist. - * - * @return \Drupal\Core\Config\StorageComparerInterface - * An object which implements the StorageComparerInterface. - */ - public function createChangelist(); - - /** - * Creates the delete changelist. - * - * @return \Drupal\Core\Config\StorageComparerInterface - * An object which implements the StorageComparerInterface. - */ - public function addChangelistDelete(); - - /** - * Creates the create changelist. - * - * @return \Drupal\Core\Config\StorageComparerInterface - * An object which implements the StorageComparerInterface. - */ - public function addChangelistCreate(); - - /** - * Creates the update changelist. - * - * @return \Drupal\Core\Config\StorageComparerInterface - * An object which implements the StorageComparerInterface. - */ - public function addChangelistUpdate(); - - /** * Recalculates the differences. * * @return \Drupal\Core\Config\StorageComparerInterface diff --git a/core/tests/Drupal/Tests/Core/Config/StorageComparerTest.php b/core/tests/Drupal/Tests/Core/Config/StorageComparerTest.php new file mode 100644 index 0000000..36915d8 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Config/StorageComparerTest.php @@ -0,0 +1,232 @@ + '', + 'name' => '\Drupal\Core\Config\StorageComparer unit test', + 'group' => 'Configuration', + ); + } + + 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); + } + + protected function getConfigData() { + $uuid = new Php(); + // Mock data using minimal data to use ConfigDependencyManger. + $this->configData = array( + // Simple config that controls configuration sync. + 'system.site' => array( + 'title' => 'Drupal', + 'uuid' => $uuid->generate(), + ), + // Config entity which requires another config entity. + 'field.instance.node.article.body' => array( + 'id' => 'node.article.body', + 'uuid' => $uuid->generate(), + 'dependencies' => array( + 'entity' => array( + 'field.field.node.body' + ), + ), + ), + // Config entity which is required by another config entity. + 'field.field.node.body' => array( + 'id' => 'node.body', + 'uuid' => $uuid->generate(), + 'dependencies' => array( + 'module' => array( + 'text', + ), + ), + ), + // Config entity not which has no dependencies on configuration. + 'views.view.frontpage' => array( + 'id' => 'frontpage', + 'uuid' => $uuid->generate(), + 'dependencies' => array( + 'module' => array( + 'node', + ), + ), + ), + // Simple config. + 'system.performance' => array( + 'stale_file_threshold' => 2592000 + ), + + ); + return $this->configData; + } + + /** + * @covers ::createChangelist + */ + public function testCreateChangelistNoChange() { + $config_data = $this->getConfigData(); + $config_files = array_keys($config_data); + $this->sourceStorage->expects($this->once()) + ->method('listAll') + ->will($this->returnValue($config_files)); + $this->targetStorage->expects($this->once()) + ->method('listAll') + ->will($this->returnValue($config_files)); + $this->sourceStorage->expects($this->once()) + ->method('readMultiple') + ->will($this->returnValue($config_data)); + $this->targetStorage->expects($this->once()) + ->method('readMultiple') + ->will($this->returnValue($config_data)); + + $this->storageComparer->createChangelist(); + $this->assertEmpty($this->storageComparer->getChangelist('create')); + $this->assertEmpty($this->storageComparer->getChangelist('delete')); + $this->assertEmpty($this->storageComparer->getChangelist('update')); + } + + /** + * @covers ::createChangelist + */ + public function testCreateChangelistCreate() { + $target_data = $source_data = $this->getConfigData(); + unset($target_data['field.field.node.body']); + unset($target_data['field.instance.node.article.body']); + unset($target_data['views.view.frontpage']); + + $this->sourceStorage->expects($this->once()) + ->method('listAll') + ->will($this->returnValue(array_keys($source_data))); + $this->targetStorage->expects($this->once()) + ->method('listAll') + ->will($this->returnValue(array_keys($target_data))); + $this->sourceStorage->expects($this->once()) + ->method('readMultiple') + ->will($this->returnValue($source_data)); + $this->targetStorage->expects($this->once()) + ->method('readMultiple') + ->will($this->returnValue($target_data)); + + $this->storageComparer->createChangelist(); + $expected = array( + 'field.field.node.body', + 'views.view.frontpage', + 'field.instance.node.article.body', + ); + $this->assertEquals($expected, $this->storageComparer->getChangelist('create')); + $this->assertEmpty($this->storageComparer->getChangelist('delete')); + $this->assertEmpty($this->storageComparer->getChangelist('update')); + } + + /** + * @covers ::createChangelist + */ + public function testCreateChangelistDelete() { + $target_data = $source_data = $this->getConfigData(); + unset($source_data['field.field.node.body']); + unset($source_data['field.instance.node.article.body']); + unset($source_data['views.view.frontpage']); + + $this->sourceStorage->expects($this->once()) + ->method('listAll') + ->will($this->returnValue(array_keys($source_data))); + $this->targetStorage->expects($this->once()) + ->method('listAll') + ->will($this->returnValue(array_keys($target_data))); + $this->sourceStorage->expects($this->once()) + ->method('readMultiple') + ->will($this->returnValue($source_data)); + $this->targetStorage->expects($this->once()) + ->method('readMultiple') + ->will($this->returnValue($target_data)); + + $this->storageComparer->createChangelist(); + $expected = array( + 'field.instance.node.article.body', + 'views.view.frontpage', + 'field.field.node.body', + ); + $this->assertEquals($expected, $this->storageComparer->getChangelist('delete')); + $this->assertEmpty($this->storageComparer->getChangelist('create')); + $this->assertEmpty($this->storageComparer->getChangelist('update')); + } + + /** + * @covers ::createChangelist + */ + public function testCreateChangelistUpdate() { + $target_data = $source_data = $this->getConfigData(); + $source_data['system.site']['title'] = 'Drupal New!'; + $source_data['field.instance.node.article.body']['new_config_key'] = 'new data'; + $source_data['field.field.node.body']['new_config_key'] = 'new data'; + + $this->sourceStorage->expects($this->once()) + ->method('listAll') + ->will($this->returnValue(array_keys($source_data))); + $this->targetStorage->expects($this->once()) + ->method('listAll') + ->will($this->returnValue(array_keys($target_data))); + $this->sourceStorage->expects($this->once()) + ->method('readMultiple') + ->will($this->returnValue($source_data)); + $this->targetStorage->expects($this->once()) + ->method('readMultiple') + ->will($this->returnValue($target_data)); + + $this->storageComparer->createChangelist(); + $expected = array( + 'field.field.node.body', + 'system.site', + 'field.instance.node.article.body', + ); + $this->assertEquals($expected, $this->storageComparer->getChangelist('update')); + $this->assertEmpty($this->storageComparer->getChangelist('create')); + $this->assertEmpty($this->storageComparer->getChangelist('delete')); + } + +}