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 379ff74db0..791fa31d0d 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/DecimalItem.php @@ -102,9 +102,67 @@ public function getConstraints() { ], ]); + $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager(); + + // To prevent a PDO exception from occurring, restrict values in the range + // allowed by databases. + $min = $this->getDefaultMinValue(); + if (!is_null($min)) { + $constraints[] = $constraint_manager->create('ComplexData', [ + 'value' => [ + 'Range' => [ + 'min' => $min, + 'minMessage' => t('%name: The integer must be larger or equal to %min.', [ + '%name' => $this->getFieldDefinition()->getLabel(), + '%min' => $min, + ]), + ], + ], + ]); + } + + $max = $this->getDefaultMaxValue(); + if (!is_null($max)) { + $constraints[] = $constraint_manager->create('ComplexData', [ + 'value' => [ + 'Range' => [ + 'max' => $max, + 'maxMessage' => t('%name: The integer must be smaller or equal to %max.', [ + '%name' => $this->getFieldDefinition()->getLabel(), + '%max' => $max, + ]), + ], + ], + ]); + } + return $constraints; } + /** + * Helper method to get the max value restricted by databases. + * + * @return float + * The maximum value allowed by database. + */ + protected function getDefaultMaxValue() { + $precision = $this->getSetting('precision'); + $scale = $this->getSetting('scale'); + + return pow(10, $precision - $scale) - + (1 / pow(10, $scale)); + } + + /** + * Helper method to get the min value restricted by databases. + * + * @return float + * The minimum value allowed by database. + */ + protected function getDefaultMinValue() { + return -$this->getDefaultMaxValue(); + } + /** * {@inheritdoc} */ @@ -139,11 +197,11 @@ public static function generateSampleValue(FieldDefinitionInterface $field_defin $max = is_numeric($settings['max']) ?: pow(10, ($precision - $scale)) - 1; $min = is_numeric($settings['min']) ?: -pow(10, ($precision - $scale)) + 1; - // Get the number of decimal digits for the $max + // Get the number of decimal digits for the $max. $decimal_digits = self::getDecimalDigits($max); // Do the same for the min and keep the higher number of decimal digits. $decimal_digits = max(self::getDecimalDigits($min), $decimal_digits); - // If $min = 1.234 and $max = 1.33 then $decimal_digits = 3 + // If $min = 1.234 and $max = 1.33 then $decimal_digits = 3. $scale = rand($decimal_digits, $scale); // @see "Example #1 Calculate a random floating-point number" in diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php index 8469126729..6414761c9b 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php @@ -61,23 +61,40 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel public function getConstraints() { $constraints = parent::getConstraints(); - // If this is an unsigned integer, add a validation constraint for the - // integer to be positive. - if ($this->getSetting('unsigned')) { - $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager(); + $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager(); + + // To prevent a PDO exception from occurring, restrict values in the range + // allowed by databases. + $min = $this->getDefaultMinValue(); + if (!is_null($min)) { $constraints[] = $constraint_manager->create('ComplexData', [ 'value' => [ 'Range' => [ - 'min' => 0, + 'min' => $min, 'minMessage' => t('%name: The integer must be larger or equal to %min.', [ '%name' => $this->getFieldDefinition()->getLabel(), - '%min' => 0, + '%min' => $min, ]), ], ], ]); } + $max = $this->getDefaultMaxValue(); + if (!is_null($max)) { + $constraints[] = $constraint_manager->create('ComplexData', [ + 'value' => [ + 'Range' => [ + 'max' => $max, + 'maxMessage' => t('%name: The integer must be smaller or equal to %max.', [ + '%name' => $this->getFieldDefinition()->getLabel(), + '%max' => $max, + ]), + ], + ], + ]); + } + return $constraints; } @@ -109,4 +126,58 @@ public static function generateSampleValue(FieldDefinitionInterface $field_defin return $values; } + /** + * Helper method to get the max value restricted by databases. + * + * @return int + * The maximum value allowed by database. + */ + protected function getDefaultMaxValue() { + if ($this->getSetting('unsigned')) { + // Each value is (2 ^ (8 * bytes) - 1). + $size_map = [ + 'normal' => 4294967295, + 'tiny' => 255, + 'small' => 65535, + 'medium' => 16777215, + 'big' => 18446744073709551615, + ]; + } + else { + // Each value is (2 ^ (8 * bytes - 1) - 1). + $size_map = [ + 'normal' => 2147483647, + 'tiny' => 127, + 'small' => 32767, + 'medium' => 8388607, + 'big' => 9223372036854775807, + ]; + } + + return $size_map[$this->getSetting('size')]; + } + + /** + * Helper method to get the min value restricted by databases. + * + * @return int + * The minimum value allowed by database. + */ + protected function getDefaultMinValue() { + if ($this->getSetting('unsigned')) { + return 0; + } + + // Each value is - (2 ^ (8 * bytes - 1)). + $size_map = [ + 'normal' => -2147483648, + 'tiny' => -128, + 'small' => -32768, + 'medium' => -8388608, + 'big' => -9223372036854775808, + ]; + + return $size_map[$this->getSetting('size')]; + } + } diff --git a/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php b/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php index 667a587a9d..59b88b81df 100644 --- a/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php +++ b/core/modules/field/tests/src/Functional/Number/NumberFieldTest.php @@ -281,6 +281,76 @@ public function testNumberIntegerField() { $this->assertSession()->elementTextContains('xpath', '//div[@content="' . $integer_value . '"]', 'ThePrefix' . $integer_value); } + /** + * Test max/min value constraints for integer fields without settings. + */ + public function testNumberIntegerFieldWithoutSettings() { + + // Create unbounded field without limits. + $field_name_unlimited = Unicode::strtolower($this->randomMachineName()); + $storage = FieldStorageConfig::create([ + 'field_name' => $field_name_unlimited, + 'entity_type' => 'entity_test', + 'type' => 'integer', + ]); + $storage->save(); + + FieldConfig::create([ + 'field_name' => $field_name_unlimited, + 'entity_type' => 'entity_test', + 'bundle' => 'entity_test', + 'settings' => [], + ])->save(); + + entity_get_form_display('entity_test', 'entity_test', 'default') + ->setComponent($field_name_unlimited, [ + 'type' => 'number', + ]) + ->save(); + entity_get_display('entity_test', 'entity_test', 'default') + ->setComponent($field_name_unlimited, [ + 'type' => 'number_integer', + ]) + ->save(); + + // Create a node and test that integer sizes are correctly validated. + // Too large of an integer is rejected. + $max_integer = 2147483647; + $this->drupalGet('entity_test/add'); + $edit = [ + "{$field_name_unlimited}[0][value]" => $max_integer + 1, + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('The integer must be smaller or equal to', 'Found too big integer.'); + + $this->drupalGet('entity_test/add'); + $edit = [ + "{$field_name_unlimited}[0][value]" => $max_integer, + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created'); + + // Too small of an integer is rejected. + $min_integer = -2147483648; + $this->drupalGet('entity_test/add'); + $edit = [ + "{$field_name_unlimited}[0][value]" => $min_integer - 1, + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + $this->assertText('The integer must be larger or equal to', 'Found too small integer.'); + + $this->drupalGet('entity_test/add'); + $edit = [ + "{$field_name_unlimited}[0][value]" => $min_integer, + ]; + $this->drupalPostForm(NULL, $edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->url, $match); + $id = $match[1]; + $this->assertText(t('entity_test @id has been created.', ['@id' => $id]), 'Entity was created'); + } + /** * Tests float field. */ diff --git a/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php b/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php index a87004ead6..1f85a1fbc9 100644 --- a/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php +++ b/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php @@ -618,7 +618,7 @@ public function testTypedDataValidation() { $field_item = $this->typedDataManager->create($definition, ['value' => 'no integer']); $violations = $field_item->validate(); - $this->assertEquals(1, $violations->count()); + $this->assertEquals(3, $violations->count()); $this->assertEquals('0.value', $violations[0]->getPropertyPath()); // Test that the field item may not be empty. @@ -661,7 +661,7 @@ public function testTypedDataValidation() { $violations = $this->typedDataManager->create($definition, [['value' => 10]])->validate(); $this->assertEquals(0, $violations->count()); $violations = $this->typedDataManager->create($definition, [['value' => 'string']])->validate(); - $this->assertEquals(1, $violations->count()); + $this->assertEquals(3, $violations->count()); $this->assertEquals('string', $violations[0]->getInvalidValue()); $this->assertSame('0.value', $violations[0]->getPropertyPath()); diff --git a/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php b/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php index 323601e074..4f19f3c4a5 100644 --- a/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php +++ b/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php @@ -57,6 +57,10 @@ public function provideTestValidate() { $data[] = [new FloatData(DataDefinition::create('float')), 1.5, TRUE]; $data[] = [new FloatData(DataDefinition::create('float')), 'test', FALSE]; $data[] = [new IntegerData(DataDefinition::create('integer')), 1, TRUE]; + $data[] = [new IntegerData(DataDefinition::create('integer')), PHP_INT_MAX, TRUE]; + $data[] = [new IntegerData(DataDefinition::create('integer')), ~PHP_INT_MAX, TRUE]; + $data[] = [new IntegerData(DataDefinition::create('integer')), PHP_INT_MAX + 1, FALSE]; + $data[] = [new IntegerData(DataDefinition::create('integer')), ~PHP_INT_MAX - 1, FALSE]; $data[] = [new IntegerData(DataDefinition::create('integer')), 1.5, FALSE]; $data[] = [new IntegerData(DataDefinition::create('integer')), 'test', FALSE]; $data[] = [new StringData(DataDefinition::create('string')), 'test', TRUE];