diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 740f316..421aa1b 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -189,13 +189,6 @@ public function isNew() { * 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 mixed * The data that was requested. */ @@ -319,8 +312,6 @@ public function set($key, $value) { if (!$this->isLoaded) { $this->load(); } - // 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. @@ -336,46 +327,6 @@ public function set($key, $value) { } /** - * 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 mixed $value - * A value being saved into the configuration system. - * - * @return string - * The value cast to a string or array. - */ - public function castValue($value) { - if (is_scalar($value) || $value === NULL) { - // 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 string $key diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index 6422e9d..a08af4b 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -140,6 +140,7 @@ protected function getDumper() { // 2 spaces for consistency with Drupal coding standards. $this->dumper->setIndentation(2); } + return $this->dumper; } @@ -162,8 +163,9 @@ protected function getParser() { */ public function encode($data) { // The level where you switch to inline YAML is set to PHP_INT_MAX to ensure - // this does not occur. - return $this->getDumper()->dump($data, PHP_INT_MAX); + // this does not occur. Also set the exceptionOnInvalidType parameter to + // TRUE, so exceptions are thrown for an invalid data type. + return $this->getDumper()->dump($data, PHP_INT_MAX, 0, TRUE); } /** diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php index 5990b2f..6310f36 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php @@ -94,18 +94,18 @@ protected function createTests() { // Ensure that default values are filled in. $expected_properties = array( 'id' => 'stark.test_block', - 'weight' => '', - 'status' => '1', + 'weight' => NULL, + 'status' => TRUE, 'langcode' => language_default()->langcode, - 'region' => '-1', + 'region' => -1, 'plugin' => 'test_html_id', 'settings' => array( - 'cache' => '1', + 'cache' => 1, 'label' => '', 'module' => 'block_test', 'label_display' => BLOCK_LABEL_VISIBLE, ), - 'visibility' => '', + 'visibility' => NULL, ); $this->assertIdentical($actual_properties, $expected_properties, 'The block properties are exported correctly.'); diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php index c0bc012..cf56ef4 100644 --- a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php +++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php @@ -164,10 +164,10 @@ function testAdmin() { $this->drupalGet('admin/config/content/formats/manage/filtered_html'); $ultra_llama_mode_checkbox = $this->xpath('//input[@type="checkbox" and @name="editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]" and @checked="checked"]'); $this->assertTrue(count($ultra_llama_mode_checkbox) === 1, 'The "Ultra llama mode" checkbox exists and is checked.'); - $expected_settings['plugins']['llama_contextual_and_button']['ultra_llama_mode'] = '1'; + $expected_settings['plugins']['llama_contextual_and_button']['ultra_llama_mode'] = 1; $editor = entity_load('editor', 'filtered_html'); $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.'); - $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.'); + $this->assertIdentical($expected_settings, $editor->settings); } } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php index d4c170c..195fba1 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\ConfigNameException; use Drupal\simpletest\DrupalUnitTestBase; +use Drupal\Core\Config\FileStorage; /** * Tests CRUD operations on configuration objects. @@ -131,7 +132,7 @@ function testCRUD() { /** * Tests the validation of configuration object names. */ - function testNameValidation() { + public function testNameValidation() { // Verify that an object name without namespace causes an exception. $name = 'nonamespace'; $message = 'Expected ConfigNameException was thrown for a name without a namespace.'; @@ -194,5 +195,56 @@ function testNameValidation() { $this->pass($message); } } -} + /** + * Tests data type handling. + */ + public function testDataTypes() { + $this->container->get('module_handler')->enable(array('config_test')); + $storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); + $name = 'config_test.types'; + $config = $this->container->get('config.factory')->get($name); + $original_content = file_get_contents($storage->getFilePath($name)); + $this->verbose('
' . $original_content . "\n" . var_export($storage->read($name), TRUE)); + + // Verify variable data types are intact. + $data = array( + 'array' => array(), + 'boolean' => TRUE, + 'exp' => 1.2e+34, + 'float' => 3.14159, + 'hex' => 0xC, + 'int' => 99, + 'octal' => 0775, + 'string' => 'string', + 'string_int' => '1', + ); + $this->assertIdentical($config->get(), $data); + + // Re-set each key using Config::set(). + foreach($data as $key => $value) { + $config->set($key, $value); + } + $config->save(); + $this->assertIdentical($config->get(), $data); + // Assert the data against the file storage. + $this->assertIdentical($storage->read($name), $data); + $this->verbose('' . file_get_contents($storage->getFilePath($name)) . var_export($storage->read($name), TRUE)); + + // Set data using config::setData(). + $config->setData($data)->save(); + $this->assertIdentical($config->get(), $data); + $this->assertIdentical($storage->read($name), $data); + + try { + $config->set('stream', fopen(__FILE__, 'r'))->save(); + $this->fail('No Exception thrown upon saving invalid data type.'); + } + catch (\Exception $e) { + $this->pass(format_string('%class thrown upon saving invalid data type.', array( + '%class' => get_class($e), + ))); + } + } + +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php index 09fd53b..f4f3860 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityTest.php @@ -150,8 +150,7 @@ function testCRUD() { // Verify that the entity was overwritten. $same_id = entity_load('config_test', $config_test->id()); $this->assertIdentical($same_id->id(), $config_test->id()); - // Note: Reloading loads from FileStorage, and FileStorage enforces strings. - $this->assertIdentical($same_id->label(), ''); + $this->assertIdentical($same_id->label(), NULL); $this->assertNotEqual($same_id->uuid(), $config_test->uuid()); // Delete the overridden entity first. diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php index f2e7965..7d4e43a 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php @@ -123,7 +123,7 @@ function testReadWriteConfig() { $this->assertEqual($config->get($true_key), '1', format_string("Boolean TRUE value returned the string '1'.")); // Read null value. - $this->assertIdentical($config->get('null'), ''); + $this->assertIdentical($config->get('null'), NULL); // Read false that had been nested in an array value $this->assertEqual($config->get($casting_array_false_value_key), '0', format_string("Nested boolean FALSE value returned the string '0'.")); 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 a2b3448..d986835 100644 --- a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php +++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php @@ -161,6 +161,30 @@ function testCRUD() { } + /** + * 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', + 'string_int' => '1', + ); + + $result = $this->storage->write($name, $data); + $this->assertIdentical($result, TRUE); + + $read_data = $this->storage->read($name); + $this->assertIdentical($read_data, $data); + } + 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..4ab2cdc --- /dev/null +++ b/core/modules/config/tests/config_test/config/config_test.types.yml @@ -0,0 +1,9 @@ +array: [] +boolean: true +exp: 1.2e+34 +float: 3.14159 +hex: 0xC +int: 99 +octal: 0775 +string: string +string_int: '1' diff --git a/core/modules/image/image.module b/core/modules/image/image.module index 5eda365..5ae08e8 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -980,8 +980,12 @@ function image_field_entity_update(FieldInterface $field) { // The value of a managed_file element can be an array if #extended == TRUE. $fid_new = (isset($field->settings['default_image']['fids']) ? $field->settings['default_image']['fids'] : $field->settings['default_image']); $fid_old = (isset($prior_field->settings['default_image']['fids']) ? $prior_field->settings['default_image']['fids'] : $prior_field->settings['default_image']); + // Ensure sure that fid_new and old are arrays, because default_image might + // be the fallback value 0, see image_field_info(). + $fid_old = (array) $fid_old; + $fid_new = (array) $fid_new; - $file_new = $fid_new ? file_load($fid_new) : FALSE; + $file_new = $fid_new ? file_load(reset($fid_new)) : FALSE; if ($fid_new != $fid_old) { @@ -993,7 +997,7 @@ function image_field_entity_update(FieldInterface $field) { } // Is there an old file? - if ($fid_old && ($file_old = file_load($fid_old[0]))) { + if ($fid_old && ($file_old = file_load(reset($fid_old)))) { file_usage()->delete($file_old, 'image', 'default_image', $field->uuid); } } diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeFormButtonsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeFormButtonsTest.php index a592c88..54fe00f 100644 --- a/core/modules/node/lib/Drupal/node/Tests/NodeFormButtonsTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/NodeFormButtonsTest.php @@ -109,7 +109,7 @@ function testNodeFormButtons() { // the initial order of buttons and/or status of the node when creating // a node. variable_set('node_options_article', array('promote')); - config('node.type.article')->set('settings.node.options.status', 0)->save(); + config('node.type.article')->set('settings.node.options.status', false)->save(); // Verify the buttons on a node add form for an administrator. $this->drupalLogin($this->admin_user); diff --git a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleEnable.php b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleEnable.php index deec030..e2ceedb 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Module/ModuleEnable.php +++ b/core/modules/system/lib/Drupal/system/Tests/Module/ModuleEnable.php @@ -32,7 +32,7 @@ public static function getInfo() { */ function testEnableUserTwice() { module_enable(array('user'), FALSE); - $this->assertIdentical(config('system.module')->get('enabled.user'), '0'); + $this->assertIdentical(config('system.module')->get('enabled.user'), 0); } /**