diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index ffddd61..4829420 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -92,11 +92,6 @@ class Config { * would return array('bar' => 'baz'). * If no key is specified, then the entire data array is returned. * - * The configuration system does not retain data types. Every saved value is - * casted to a string. In most cases this is not an issue; however, it can - * cause issues with Booleans, which are casted to "1" (TRUE) or "0" (FALSE). - * In particular, code relying on === or !== will no longer function properly. - * * @see http://php.net/manual/language.operators.comparison.php. * * @return @@ -149,9 +144,6 @@ class Config { * @todo */ public function set($key, $value) { - // Type-cast value into a string. - $value = $this->castValue($value); - // The dot/period is a reserved character; it may appear between keys, but // not within keys. $parts = explode('.', $key); @@ -165,45 +157,6 @@ class Config { } /** - * Casts a saved value to a string. - * - * The configuration system only saves strings or arrays. Any scalar - * non-string value is cast to a string. The one exception is boolean FALSE - * which would normally become '' when cast to a string, but is manually - * cast to '0' here for convenience and consistency. - * - * Any non-scalar value that is not an array (aka objects) gets cast - * to an array. - * - * @param $value - * A value being saved into the configuration system. - * @param $value - * The value cast to a string or array. - */ - public function castValue($value) { - if (is_scalar($value)) { - // Handle special case of FALSE, which should be '0' instead of ''. - if ($value === FALSE) { - $value = '0'; - } - else { - $value = (string) $value; - } - } - else { - // Any non-scalar value must be an array. - if (!is_array($value)) { - $value = (array) $value; - } - // Recurse into any nested keys. - foreach ($value as $key => $nested_value) { - $value[$key] = $this->castValue($nested_value); - } - } - return $value; - } - - /** * Unsets value in this config object. * * @param $key diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index f30011c..a6c4322 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -13,7 +13,7 @@ use Exception; /** * Defines the Database storage controller. */ -class DatabaseStorage implements StorageInterface { +class DatabaseStorage extends StorageBase { /** * Database connection options for this storage controller. @@ -77,6 +77,7 @@ class DatabaseStorage implements StorageInterface { * @todo Ignore slave targets for data manipulation operations. */ public function write($name, array $data) { + $this->validateData($name, $data); $data = $this->encode($data); $options = array('return' => Database::RETURN_AFFECTED) + $this->options; return (bool) $this->getConnection()->merge('config', $options) diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index 033555d..0fc800e 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -12,7 +12,7 @@ use Symfony\Component\Yaml\Yaml; /** * Defines the file storage controller. */ -class FileStorage implements StorageInterface { +class FileStorage extends StorageBase { /** * Configuration options for this storage controller. @@ -86,6 +86,7 @@ class FileStorage implements StorageInterface { * @throws Drupal\Core\Config\StorageException */ public function write($name, array $data) { + $this->validateData($name, $data); $data = $this->encode($data); $status = @file_put_contents($this->getFilePath($name), $data); if ($status === FALSE) { diff --git a/core/lib/Drupal/Core/Config/NullStorage.php b/core/lib/Drupal/Core/Config/NullStorage.php index fede4f0..5e58239 100644 --- a/core/lib/Drupal/Core/Config/NullStorage.php +++ b/core/lib/Drupal/Core/Config/NullStorage.php @@ -21,7 +21,7 @@ namespace Drupal\Core\Config; * * This also can be used for testing purposes. */ -class NullStorage implements StorageInterface { +class NullStorage extends StorageBase { /** * Implements Drupal\Core\Config\StorageInterface::__construct(). */ diff --git a/core/lib/Drupal/Core/Config/StorageBase.php b/core/lib/Drupal/Core/Config/StorageBase.php new file mode 100644 index 0000000..65cb056 --- /dev/null +++ b/core/lib/Drupal/Core/Config/StorageBase.php @@ -0,0 +1,35 @@ + diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php index 806ee87..051092b 100644 --- a/core/lib/Drupal/Core/Config/StorageInterface.php +++ b/core/lib/Drupal/Core/Config/StorageInterface.php @@ -89,6 +89,36 @@ interface StorageInterface { public static function decode($raw); /** + * Validates configuration data. + * + * @param string $name + * The name of a configuration object to validate. + * @param array $data + * The configuration data to validate. + * + * @throws StorageException + */ + public function validateData($name, array $data); + + /** + * Validates a configuration value. + * + * Helper callback for array_walk_recursive(). + * + * @param mixed $value + * The configuration data value to validate. + * @param string $key + * The key of the value. + * @param string $name + * The name of the configuration object. + * + * @throws StorageException + * + * @see StorageBase::validateData() + */ + public function validateValue($value, $key, $name); + + /** * Gets configuration object names starting with a given prefix. * * Given the following configuration objects: diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php index 37aa854..694b4f0 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php @@ -8,6 +8,7 @@ namespace Drupal\config\Tests; use Drupal\Core\Config\DatabaseStorage; +use Drupal\Core\Config\StorageException; use Drupal\simpletest\WebTestBase; /** @@ -22,6 +23,10 @@ class ConfigCRUDTest extends WebTestBase { ); } + function setUp() { + parent::setUp(array('config_test')); + } + /** * Tests CRUD operations. */ @@ -112,4 +117,72 @@ class ConfigCRUDTest extends WebTestBase { // their order must be identical. $this->assertIdentical($new_config->get(), $config->get()); } + + /** + * Tests data type preservation. + */ + function testDataTypePreservation() { + $config = config('config_test.types'); + + // Test typed varaibles. + $data = array( + 'array' => array(), + 'boolean' => TRUE, + 'exp' => 1.2e+34, + 'float' => 3.14159, + 'hex' => 0xC, + 'int' => 99, + 'octal' => 0775, + 'string' => 'string', + ); + + // Test data provided by config_test.types.yml. + $this->assertIdentical($config->get(), $data); + + // Set each key using config::set(). + foreach($data as $key => $value) { + $config->set($key, $value); + } + $config->save(); + $this->assertIdentical($config->get(), $data); + + // Set data using config::setData(). + $config->setData($data)->save(); + $this->assertIdentical($config->get(), $data); + } + + /** + * Tests data type validation. + */ + function testDataTypeValidation() { + $config = config('config_test.invalid_data'); + + // Config can't save resources. + $data = array( + 'valid_data_type' => 1, + 'invalid_type' => fopen(__FILE__, 'r'), + ); + // Test using config::set() + foreach ($data as $key => $value) { + $config->set($key, $value); + } + try { + $config->save(); + } + catch (StorageException $e) { + $message = $e->getMessage(); + } + $this->assertEqual($message, 'Unsupported non-scalar value of type resource in key invalid_type in config object config_test.invalid_data'); + + // Config can't save objects. + $data['invalid_type'] = new \stdClass(); + // Test using config::setData(). + try { + $config->setData($data)->save(); + } + catch (StorageException $e) { + $message = $e->getMessage(); + } + $this->assertEqual($message, 'Unsupported non-scalar value of type object in key invalid_type in config object config_test.invalid_data'); + } } diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php index 2dbc627..db7480d 100644 --- a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php +++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php @@ -8,6 +8,7 @@ namespace Drupal\config\Tests\Storage; use Drupal\simpletest\WebTestBase; +use Drupal\Core\Config\StorageException; /** * Base class for testing storage controller operations. @@ -111,6 +112,46 @@ abstract class ConfigStorageTestBase extends WebTestBase { } } + /** + * Tests storage controller writing and reading data preserving data type. + */ + function testDataTypes() { + $name = 'config_test.types'; + $data = array( + 'array' => array(), + 'boolean' => TRUE, + 'exp' => 1.2e+34, + 'float' => 3.14159, + 'hex' => 0xC, + 'int' => 99, + 'octal' => 0775, + 'string' => 'string', + ); + + $result = $this->storage->write($name, $data); + $this->assertIdentical($result, TRUE); + + $read_data = $this->storage->read($name); + $this->assertIdentical($read_data, $data); + } + + /** + * Tests storage controller writing an invalid data type. + */ + function testInvalidData() { + $name = 'config_test.invalid_data'; + $data = array( + 'invalid_key' => new \stdClass(), + ); + try { + $data = $this->storage->write($name, $data); + } + catch (StorageException $e) { + $message = $e->getMessage(); + } + $this->assertEqual($message, sprintf('Unsupported non-scalar value of type object in key invalid_key in config object %s', $name)); + } + abstract protected function read($name); abstract protected function insert($name, $data); diff --git a/core/modules/config/tests/config_test/config/config_test.types.yml b/core/modules/config/tests/config_test/config/config_test.types.yml new file mode 100644 index 0000000..d4005b1 --- /dev/null +++ b/core/modules/config/tests/config_test/config/config_test.types.yml @@ -0,0 +1,8 @@ +array: [] +boolean: true +exp: 1.2e+34 +float: 3.14159 +hex: 0xC +int: 99 +octal: 0775 +string: string