diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 7c150e9..e2ee86f 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -150,7 +150,7 @@ public function isNew() { * @return mixed * The data that was requested. */ - public function get($key = '') { + public function &get($key = '') { if (!isset($this->overriddenData)) { $this->setOverriddenData(); } @@ -159,12 +159,19 @@ public function get($key = '') { } else { $parts = explode('.', $key); + $null = NULL; if (count($parts) == 1) { - return isset($this->overriddenData[$key]) ? $this->overriddenData[$key] : NULL; + if (isset($this->overriddenData[$key])) { + return $this->overriddenData[$key]; + } + return $null; } else { $value = NestedArray::getValue($this->overriddenData, $parts, $key_exists); - return $key_exists ? $value : NULL; + if ($key_exists) { + return $value; + } + return $null; } } } @@ -299,21 +306,53 @@ public function castValue($value) { } /** + * Sorts all data or a subset of data in this config object. + * + * @param string $key + * A string that maps to a key within the configuration data. Pass NULL or + * an empty string to sort all data. See Config::get() for details. + * @param string $callback + * A uasort() callback function to sort the data with. The following + * primitive array sorting PHP functions are supported as well: + * sort(), asort(), arsort(), ksort(), krsort(), natsort(), natcasesort(). + * + * @return Drupal\Core\Config\Config + * The configuration object. + */ + public function sort($key, $callback) { + $data = &$this->get($key); + // Provide built-in support for most common primitive sorting cases. + if (in_array($callback, array('sort', 'asort', 'arsort', 'ksort', 'krsort', 'natsort', 'natcasesort'))) { + $callback($data); + } + else { + uasort($data, $callback); + } + return $this; + } + + /** * Unsets value in this config object. * * @param string $key - * Name of the key whose value should be unset. + * (optional) The name of the key whose value should be unset. If omitted, + * the entire config object is emptied. * * @return Drupal\Core\Config\Config * The configuration object. */ - public function clear($key) { - $parts = explode('.', $key); - if (count($parts) == 1) { - unset($this->data[$key]); + public function clear($key = NULL) { + if (!isset($key)) { + $this->data = array(); } else { - NestedArray::unsetValue($this->data, $parts); + $parts = explode('.', $key); + if (count($parts) == 1) { + unset($this->data[$key]); + } + else { + NestedArray::unsetValue($this->data, $parts); + } } $this->resetOverriddenData(); return $this; @@ -346,7 +385,6 @@ public function load() { * The configuration object. */ public function save() { - $this->sortByKey($this->data); $this->storage->write($this->name, $this->data); $this->isNew = FALSE; $this->notify('save'); @@ -370,26 +408,6 @@ public function rename($new_name) { } /** - * Sorts all keys in configuration data. - * - * Ensures that re-inserted keys appear in the same location as before, in - * order to ensure an identical order regardless of storage controller. - * A consistent order is important for any storage that allows any kind of - * diff operation. - * - * @param array $data - * An associative array to sort recursively by key name. - */ - public function sortByKey(array &$data) { - ksort($data); - foreach ($data as &$value) { - if (is_array($value)) { - $this->sortByKey($value); - } - } - } - - /** * Deletes the configuration object. * * @return Drupal\Core\Config\Config diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index e82eb7f..b007968 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -274,6 +274,10 @@ public function save(EntityInterface $entity) { $this->preSave($entity); $this->invokeHook('presave', $entity); + // Clear out any possibly existing keys in an existing configuration object, + // so any potentially stale keys are removed. + $config->clear(); + // Configuration objects do not have a schema. Extract all key names from // class properties. $class_info = new \ReflectionClass($entity); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php index 9eea495..4573cb5 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php @@ -93,7 +93,7 @@ function testCRUD() { } /** - * Tests Drupal\Core\Config\Config::sortByKey(). + * Tests key injection order. */ function testDataKeySort() { $config = config('config_test.keysort'); @@ -106,8 +106,6 @@ function testDataKeySort() { // Load the configuration data into a new object. $new_config = config('config_test.keysort'); - // Clear the 'new' key that came first. - $new_config->clear('new'); // Add a new 'new' key and save. $new_config->set('new', 'Value to be replaced'); $new_config->save(); @@ -117,5 +115,35 @@ function testDataKeySort() { // strict comparison, which means that keys and values must be identical and // their order must be identical. $this->assertIdentical($new_config->get(), $config->get()); + + // Verify that Config::sort() sorts data correctly. + $data = array( + 'foo' => array(3,2,1), + 'bar' => array( + 'two' => array('weight' => 2), + 'one' => array('weight' => 1), + ), + ); + $config = config('config_test.sort') + ->setData($data) + ->save(); + + // Sort 'foo' with a regular sort(). (not using asort() here for simplicity.) + $this->assertIdentical($config->sort('foo', 'sort')->get('foo'), array(1,2,3)); + + // Sort 'bar' by weight. + $this->assertIdentical($config->sort('bar', 'drupal_sort_weight')->get('bar'), array( + 'one' => array('weight' => 1), + 'two' => array('weight' => 2), + )); + + // Sort top-level by key names. + $this->assertIdentical($config->sort('', 'ksort')->get(), array( + 'bar' => array( + 'one' => array('weight' => 1), + 'two' => array('weight' => 2), + ), + 'foo' => array(1,2,3), + )); } } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php index 532979c..58cb605 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php @@ -85,6 +85,19 @@ function testCRUD() { $this->assertResponse(200); $this->assertNoText($label1); $this->assertText($label3); + $id = $edit['id']; + + // Add an orphan key to the configuration entity's object. + $config = config('config_test.dynamic.' . $id); + $config->set('foo', 'bar')->save(); + + // Re-save the configuration entity. + $this->drupalPost('admin/structure/config_test/manage/' . $id, array(), 'Save'); + $this->assertResponse(200); + + // Verify that the orphan key was destroyed. + $config = config('config_test.dynamic.' . $id); + $this->assertNull($config->get('foo')); } } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php index 9316962..2266732 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php @@ -128,10 +128,10 @@ function testNew() { $staging->write($name, $original_name_data); $original_dynamic_data = array( 'id' => 'new', + 'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651', 'label' => 'New', - 'langcode' => 'und', 'style' => '', - 'uuid' => '30df59bd-7b03-4cf7-bb35-d42fc49f0651', + 'langcode' => 'und', ); $staging->write($dynamic_name, $original_dynamic_data); $this->assertIdentical($staging->exists($name), TRUE, $name . ' found.');