diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php index 3de0666d35b1f7e4999bf9c99ae906428b0e25e0..98f0f831810072fdbdf940f74e83e4fdffbc8015 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php @@ -40,7 +40,7 @@ public static function defaultStorageSettings() { * {@inheritdoc} */ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) { - $properties['value'] = DataDefinition::create('string') + $properties['value'] = DataDefinition::create('decimal') ->setLabel(new TranslatableMarkup('Decimal value')) ->setRequired(TRUE); @@ -92,24 +92,6 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state return $element; } - /** - * {@inheritdoc} - */ - public function getConstraints() { - $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager(); - $constraints = parent::getConstraints(); - - $constraints[] = $constraint_manager->create('ComplexData', [ - 'value' => [ - 'Regex' => [ - 'pattern' => '/^[+-]?((\d+(\.\d*)?)|(\.\d+))$/i', - ], - ], - ]); - - return $constraints; - } - /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/DecimalData.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/DecimalData.php new file mode 100644 index 0000000000000000000000000000000000000000..534207eef0c5258d015aac3b44d2ce1378ea58e1 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/DecimalData.php @@ -0,0 +1,28 @@ +<?php + +namespace Drupal\Core\TypedData\Plugin\DataType; + +use Drupal\Core\TypedData\Type\DecimalInterface; + +/** + * The decimal data type. + * + * Decimal type is stored as "decimal" in the relational database. Because PHP + * does not have a primitive type decimal and using float can result in + * unexpected rounding behavior, it is implemented and displayed as string. + * + * @DataType( + * id = "decimal", + * label = @Translation("Decimal") + * ) + */ +class DecimalData extends StringData implements DecimalInterface { + + /** + * {@inheritdoc} + */ + public function getCastedValue() { + return $this->getString() ?: '0.0'; + } + +} diff --git a/core/lib/Drupal/Core/TypedData/Type/DecimalInterface.php b/core/lib/Drupal/Core/TypedData/Type/DecimalInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c7a09a6b4677fd5a4d6f73e65d4bd8befd1d3930 --- /dev/null +++ b/core/lib/Drupal/Core/TypedData/Type/DecimalInterface.php @@ -0,0 +1,17 @@ +<?php + +namespace Drupal\Core\TypedData\Type; + +use Drupal\Core\TypedData\PrimitiveInterface; + +/** + * Interface for decimal numbers. + * + * The plain value of a decimal is a PHP string. For setting the value + * any PHP variable that casts to an numeric string may be passed. + * + * @ingroup typed_data + */ +interface DecimalInterface extends PrimitiveInterface { + +} diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php index aa9c5bb4057c77a35a6f69050b547d01a77e726f..5533be502931d753d3a44ba0951302b5f6551099 100644 --- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php @@ -5,6 +5,7 @@ use Drupal\Core\TypedData\Type\BinaryInterface; use Drupal\Core\TypedData\Type\BooleanInterface; use Drupal\Core\TypedData\Type\DateTimeInterface; +use Drupal\Core\TypedData\Type\DecimalInterface; use Drupal\Core\TypedData\Type\DurationInterface; use Drupal\Core\TypedData\Type\FloatInterface; use Drupal\Core\TypedData\Type\IntegerInterface; @@ -48,6 +49,9 @@ public function validate($value, Constraint $constraint) { if ($typed_data instanceof IntegerInterface && filter_var($value, FILTER_VALIDATE_INT) === FALSE) { $valid = FALSE; } + if ($typed_data instanceof DecimalInterface && !preg_match('/^[+-]?((\d+(\.\d*)?)|(\.\d+))$/i', $value)) { + $valid = FALSE; + } if ($typed_data instanceof StringInterface && !is_scalar($value) && !($value instanceof MarkupInterface)) { $valid = FALSE; } diff --git a/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php b/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php index 30acca31c269db048f3dd62757a7383ff6af4caa..147f39a1d581e458b177832509644c2eaafc8bbb 100644 --- a/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php +++ b/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php @@ -10,6 +10,7 @@ use Drupal\Core\TypedData\Type\BinaryInterface; use Drupal\Core\TypedData\Type\BooleanInterface; use Drupal\Core\TypedData\Type\DateTimeInterface; +use Drupal\Core\TypedData\Type\DecimalInterface; use Drupal\Core\TypedData\Type\DurationInterface; use Drupal\Core\TypedData\Type\FloatInterface; use Drupal\Core\TypedData\Type\IntegerInterface; @@ -118,6 +119,27 @@ public function testGetAndSet() { $typed_data->setValue('invalid'); $this->assertEquals(1, $typed_data->validate()->count(), 'Validation detected invalid value.'); + // Decimal type. + $value = (string) (mt_rand(1, 10000) / 100); + $typed_data = $this->createTypedData(['type' => 'decimal'], $value); + $this->assertInstanceOf(DecimalInterface::class, $typed_data); + $this->assertSame($value, $typed_data->getValue(), 'Decimal value was fetched.'); + $this->assertEquals(0, $typed_data->validate()->count()); + $new_value = (string) (mt_rand(1, 10000) / 100); + $typed_data->setValue($new_value); + $this->assertSame($new_value, $typed_data->getValue(), 'Decimal value was changed.'); + $this->assertIsString($typed_data->getString()); + $this->assertEquals(0, $typed_data->validate()->count()); + $typed_data->setValue(NULL); + $this->assertNull($typed_data->getValue(), 'Decimal wrapper is null-able.'); + $this->assertEquals(0, $typed_data->validate()->count()); + $typed_data->setValue(0); + $this->assertSame('0.0', $typed_data->getCastedValue(), '0.0 casted value was fetched.'); + $typed_data->setValue('1337e0'); + $this->assertEquals(1, $typed_data->validate()->count(), 'Scientific notation is not allowed in numeric type.'); + $typed_data->setValue('invalid'); + $this->assertEquals(1, $typed_data->validate()->count(), 'Validation detected invalid value.'); + // Float type. $value = 123.45; $typed_data = $this->createTypedData(['type' => 'float'], $value);