diff --git a/core/includes/schema.inc b/core/includes/schema.inc index 978ec2307af5..0129d19334a7 100644 --- a/core/includes/schema.inc +++ b/core/includes/schema.inc @@ -5,6 +5,8 @@ * Schema API handling functions. */ +use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema; + /** * @addtogroup schemaapi * @{ @@ -215,21 +217,15 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU * * @return mixed * The converted value. + * + * @deprecated in drupal:8.8.0 and will be removed from drupal:9.0.0. Use + * \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::castValue() instead. + * + * @see https://www.drupal.org/node/3051983 */ function drupal_schema_get_field_value(array $info, $value) { - // Preserve legal NULL values. - if (isset($value) || !empty($info['not null'])) { - if ($info['type'] == 'int' || $info['type'] == 'serial') { - $value = (int) $value; - } - elseif ($info['type'] == 'float') { - $value = (float) $value; - } - elseif (!is_array($value)) { - $value = (string) $value; - } - } - return $value; + @trigger_error('drupal_schema_get_field_value() is deprecated in drupal:8.8.0. It will be removed from drupal:9.0.0. Use \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::castValue($info, $value) instead. See https://www.drupal.org/node/3051983', E_USER_DEPRECATED); + return SqlContentEntityStorageSchema::castValue($info, $value); } /** diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index edf338c9ee09..77edfc117e17 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -1097,7 +1097,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt // Do not set serial fields if we do not have a value. This supports all // SQL database drivers. // @see https://www.drupal.org/node/2279395 - $value = drupal_schema_get_field_value($definition->getSchema()['columns'][$column_name], $value); + $value = SqlContentEntityStorageSchema::castValue($definition->getSchema()['columns'][$column_name], $value); if (!(empty($value) && $this->isColumnSerial($table_name, $schema_name))) { $record->$schema_name = $value; } @@ -1398,7 +1398,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt if (!empty($attributes['serialize'])) { $value = serialize($value); } - $record[$column_name] = drupal_schema_get_field_value($attributes, $value); + $record[$column_name] = SqlContentEntityStorageSchema::castValue($attributes, $value); } $query->values($record); if ($this->entityType->isRevisionable()) { diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php index b2d1d291671d..90d05e2438f0 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php @@ -2099,7 +2099,7 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage // Use the initial value of the field storage, if available. if ($initial_value && isset($initial_value[$field_column_name])) { - $schema['fields'][$schema_field_name]['initial'] = drupal_schema_get_field_value($column_schema, $initial_value[$field_column_name]); + $schema['fields'][$schema_field_name]['initial'] = SqlContentEntityStorageSchema::castValue($column_schema, $initial_value[$field_column_name]); } if (!empty($initial_value_from_field)) { $schema['fields'][$schema_field_name]['initial_from_field'] = $initial_value_from_field[$field_column_name]; @@ -2506,4 +2506,40 @@ class SqlContentEntityStorageSchema implements DynamicallyFieldableEntityStorage $schema_handler->addUniqueKey($table, $name, $specifier); } + /** + * Typecasts values to proper datatypes. + * + * MySQL PDO silently casts, e.g. FALSE and '' to 0, when inserting the value + * into an integer column, but PostgreSQL PDO does not. Use the schema + * information to correctly typecast the value. + * + * @param array $info + * An array describing the schema field info. See hook_schema() and + * https://www.drupal.org/node/146843 for details. + * @param mixed $value + * The value to be converted. + * + * @return mixed + * The converted value. + * + * @internal + * + * @see hook_schema() + * @see https://www.drupal.org/node/146843 + */ + public static function castValue(array $info, $value) { + // Preserve legal NULL values. + if (isset($value) || !empty($info['not null'])) { + if ($info['type'] === 'int' || $info['type'] === 'serial') { + return (int) $value; + } + elseif ($info['type'] === 'float') { + return (float) $value; + } + return (string) $value; + } + + return $value; + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Database/SchemaLegacyTest.php b/core/tests/Drupal/KernelTests/Core/Database/SchemaLegacyTest.php new file mode 100644 index 000000000000..35fa7f1aa644 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Database/SchemaLegacyTest.php @@ -0,0 +1,26 @@ + 'int']; + $value = 1.1; + $this->assertEquals(SqlContentEntityStorageSchema::castValue($info, $value), drupal_schema_get_field_value($info, $value)); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php index ad30bcae795f..397e1af52009 100644 --- a/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityFieldManager; use Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface; use Drupal\Core\Entity\EntityTypeManager; use Drupal\Core\Entity\Sql\DefaultTableMapping; +use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema; use Drupal\Tests\UnitTestCase; /** @@ -1562,4 +1563,121 @@ class SqlContentEntityStorageSchemaTest extends UnitTestCase { ); } + /** + * Tests various value casts depending on column schema. + * + * @param mixed $expected + * The expected value. + * @param mixed $value + * The tested value. + * @param array $schema + * The schema for the table column. + * + * @dataProvider providerSchemaCastValue + * @covers ::castValue + */ + public function testCastValue($expected, $value, array $schema) { + $this->assertSame($expected, SqlContentEntityStorageSchema::castValue($schema, $value)); + } + + /** + * Provides data for testCastValue(). + */ + public function providerSchemaCastValue() { + $cases = []; + // Tests NULL values. + $cases[] = [ + NULL, + NULL, + [ + 'not null' => FALSE, + ], + ]; + $cases[] = [ + 0, + NULL, + [ + 'not null' => TRUE, + 'type' => 'int', + ], + ]; + $cases[] = [ + 0, + NULL, + [ + 'not null' => TRUE, + 'type' => 'serial', + ], + ]; + $cases[] = [ + 0.0, + NULL, + [ + 'not null' => TRUE, + 'type' => 'float', + ], + ]; + $cases[] = [ + '', + NULL, + [ + 'not null' => TRUE, + 'type' => 'varchar', + ], + ]; + // Tests cast to int and serial. + $cases[] = [ + 1, + '1.001', + [ + 'type' => 'int', + ], + ]; + $cases[] = [ + 2, + 2.6, + [ + 'type' => 'int', + ], + ]; + $cases[] = [ + 3, + '3.6', + [ + 'type' => 'serial', + ], + ]; + // Tests float. + $cases[] = [ + 1.001, + '1.001', + [ + 'type' => 'float', + ], + ]; + $cases[] = [ + 2.6, + 2.6, + [ + 'type' => 'float', + ], + ]; + // Tests other column types casts to string. + $cases[] = [ + '1', + 1, + [ + 'type' => 'varchar', + ], + ]; + $cases[] = [ + '2', + '2', + [ + 'type' => 'varchar', + ], + ]; + return $cases; + } + }