Problem/Motivation

PHP's empty() function is not enough to check for $settings['min'] or $settings['max'] for numeric item - \Drupal\Core\Field\Plugin\Field\FieldType\NumericItemBase::getConstraints(), because their value can be numeric zero, and in such case constraint is not returned.

This results in possible invalid numeric values for items that have min or max boundary set to 0.

Rough example:

Custom entity class, baseFieldDefinitions()

    $fields['hour'] = BaseFieldDefinition::create('integer')
      ->setLabel(new TranslatableMarkup('Hour'))
      ->setSetting('min', 0)
      ->setSetting('max', 23);

Custom code

  $e = CustomEntity::create([ 'hour' => -100 ]);
  $violations = $e->validate();

Validation passes w/o errors, because NumericItemBase::getConstraints() does not return constraint to check for minimum.

Proposed resolution

Replace !empty() check with check for non-empty string (because an empty string is default value of min/max settings):

if ($settings['min'] !== '') {}

CommentFileSizeAuthor
#26 interdiff-24-26.txt3.01 KBdrugan
#26 2838121-26.patch3.76 KBdrugan
#24 2838121-24.patch4.3 KBdrugan
#19 2838121-19.patch1.87 KBjoe_carvajal
#10 interdiff-7-10.txt1.62 KBshadcn
#10 2838121-10.patch1.89 KBshadcn
#7 2838121-7.patch1.95 KBAnonymous (not verified)
#4 2838121-4.patch1.88 KBAnonymous (not verified)
#4 2838121-4-test-only.patch953 bytesAnonymous (not verified)

Comments

pingwin4eg created an issue. See original summary.

Anonymous’s picture

What if we replace the empty to the isEmpty? E.g. for cases where min/max = NULL?

pingwin4eg’s picture

Title: Numeric item min/max settings ignored if 0 » Numeric item min/max settings ignored if set to 0
Issue summary: View changes

@vaplas isEmpty() checks the $value property of an object, but the bug is in settings, not the value. Also min/max are never NULL. If min/max settings are not set, then default values of $settings['min'] and $settings['max'] are empty strings.

Anonymous’s picture

Status: Active » Needs review
StatusFileSize
new953 bytes
new1.88 KB

@pingwin4eg, thanks for the explanation. Your suggestion makes sense to me.

The last submitted patch, 4: 2838121-4-test-only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 4: 2838121-4.patch, failed testing.

Anonymous’s picture

Status: Needs work » Needs review
StatusFileSize
new1.95 KB

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

amateescu’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php
    @@ -79,7 +79,7 @@ public function getConstraints() {
    -    if (!empty($settings['min'])) {
    +    if (!empty($settings['min']) || is_numeric($settings['min'])) {
    
    @@ -91,7 +91,7 @@ public function getConstraints() {
    -    if (!empty($settings['max'])) {
    +    if (!empty($settings['max']) || is_numeric($settings['max'])) {
    

    Since is_numeric() returns FALSE (https://3v4l.org/523Qg) for NULL and '' (an empty string), let's drop the empty() check and just keep the is_numeric() one.

  2. +++ b/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php
    @@ -629,6 +629,14 @@ public function testTypedDataValidation() {
    +      ->setSettings(array('min' => 0, 'max' => 0));
    

    Let's use the short array syntax here.

shadcn’s picture

Status: Needs work » Needs review
StatusFileSize
new1.89 KB
new1.62 KB

Updated as per #9

amateescu’s picture

Version: 8.4.x-dev » 8.3.x-dev
Status: Needs review » Reviewed & tested by the community

Great!

cilefen’s picture

Status: Reviewed & tested by the community » Needs review

I am definitely for this fix in principle, but I noticed that one cannot POST out-of-range values even with the max or min set to 0, because the Drupal\Core\Render\Element\Number::validateNumber prevents it with its own validation code. So we have two range validations in core: one for form posting and a lower-level one for fields themselves when we are not dealing with forms. I find this odd. That's not a reason not to fix this, but I'd like to hear if anybody knows the history and would like to sort this out, maybe in a follow-up.

My question is: Why isn't Drupal\Core\Render\Element\Number::validateNumber using the same constraint code as NumericItemBase and is that something we want?

edited

drugan’s picture

This issue is fixed here:

#2816859: Allow the 'step' to be configured as a NumberWidget setting

Additionally per form display mode number widget settings are introduced. Also, all the number field types now have unsigned storage setting on the UI. Also, the size storage setting is exposed on the UI.

pingwin4eg’s picture

@drugan Actually, not. I reviewed your patch from #2816859-25: Allow the 'step' to be configured as a NumberWidget setting (the part with NumericItemBase::getConstraints()) and there you do the following:

+    $min = is_numeric($settings['min']) ? Numeric::toString($settings['min']) : NULL;
+    $max = is_numeric($settings['max']) ? Numeric::toString($settings['max']) : NULL;
 
-    if (!empty($settings['min'])) {
-      $min = $settings['min'];
+    if ($min) {
       $constraints[] = $constraint_manager->create('ComplexData', [

and your Numeric::toString basically does: return (string) $number;
Now imagine there's a 0 (zero) in that setting. Constraints are still not returned.

P.S.: That's a big patch, in which you make a lot of changes and introduce new APIs. IMHO you should break it down into multiple smaller issues (like this one). This way it'd be faster for others to review/rewrite small patches.

pingwin4eg’s picture

Version: 8.3.x-dev » 8.4.x-dev
Status: Needs review » Reviewed & tested by the community

So where did we stuck here?

@cilefen Concerning your worries, I think that should not hold this fix. A follow-up issue can be created.

#10 patch looks good to me too.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 10: 2838121-10.patch, failed testing.

pingwin4eg’s picture

Issue tags: +Needs reroll

Fail reason: Line numbers doesn't match.

joe_carvajal’s picture

Assigned: Unassigned » joe_carvajal

I'll reroll it!

joe_carvajal’s picture

Issue tags: +DevDaysSeville
StatusFileSize
new1.87 KB

Hum, looks strange, but the patch applied perfectly for me. I reupload it with a minor difference (array short syntax and commit ref id).

joe_carvajal’s picture

Status: Needs work » Needs review
pingwin4eg’s picture

Assigned: joe_carvajal » Unassigned
Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs reroll

Then I guess the dreditor (or cgit) shows wrong line numbers, and patch #10 failed because of array syntax (though it's in unchanged lines).

Current Drupal core - if (!empty($settings['min'])) { is on 82nd line in cgit. And dreditor shows it is on 83rd.

drugan’s picture

@pingwin4eg

Sorry for getting you confused. Yes, you're right there: if $min = '0' then the constraint will not work. I am going to make this change in my patch.

BTW, I am also subscribing for RTBC on the #10 patch.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php
@@ -629,6 +629,14 @@ public function testTypedDataValidation() {
+
+    // Test validating when settings min/max = 0
+    $definition = BaseFieldDefinition::create('integer')
+      ->setSettings(['min' => 0, 'max' => 0]);
+    $violations = $this->typedDataManager->create($definition, 1)->validate();
+    $this->assertEqual($violations->count(), 1);
+    $violations = $this->typedDataManager->create($definition, -1)->validate();
+    $this->assertEqual($violations->count(), 1);

I think we should add further tests of what happens when one is not set. I.e something like:

    $definition = BaseFieldDefinition::create('integer')
      ->setSettings(['min' => 5]);
    $violations = $this->typedDataManager->create($definition, 6)->validate();
    $this->assertEqual($violations->count(), 0);
    $violations = $this->typedDataManager->create($definition, -1)->validate();
    $this->assertEqual($violations->count(), 1);
    $violations = $this->typedDataManager->create($definition, 0)->validate();
    $this->assertEqual($violations->count(), 1);

For both min and max case.

Also when both min and max are 0 I think it is worth adding something like:

    $violations = $this->typedDataManager->create($definition, 0)->validate();
    $this->assertEqual($violations->count(), 0);

I spent a long time thinking about whether this is the right place for this test and finally concluded it is after seeing the tests in \Drupal\KernelTests\Core\TypedData\TypedDataTest::testGetAndSet().

drugan’s picture

Status: Needs work » Needs review
StatusFileSize
new4.3 KB

@alexpott

Did you mean this?

alexpott’s picture

Status: Needs review » Needs work

@drugan yep the new tests are looking good.

  1. +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php
    @@ -78,26 +78,26 @@ public function getConstraints() {
    -            'minMessage' => t('%name: the value may be no less than %min.', ['%name' => $label, '%min' => $min]),
    +            'minMessage' => $this->t('%name: the value may be no less than %min.', ['%name' => $label, '%min' => $min]),
    ...
    -            'maxMessage' => t('%name: the value may be no greater than %max.', ['%name' => $label, '%max' => $max]),
    +            'maxMessage' => $this->t('%name: the value may be no greater than %max.', ['%name' => $label, '%max' => $max]),
    

    This change whilst technically correct is not in scope for this patch.

  2. +++ b/core/tests/Drupal/KernelTests/Core/TypedData/TypedDataTest.php
    @@ -114,6 +114,33 @@ public function testGetAndSet() {
    +    $violations = $this->typedDataManager->create($definition, 0)->validate();
    +    $this->assertEqual($violations->count(), 1);
    ...
    +    $violations = $this->typedDataManager->create($definition, 0)->validate();
    +    $this->assertEqual($violations->count(), 0);
    

    Thinks about the code we are test - rather than validating a 0 value here we should use a value of 5.

drugan’s picture

Status: Needs work » Needs review
StatusFileSize
new3.76 KB
new3.01 KB

@alexpott

Yes, I agree on that. We should check the value which is equal to a setting, then the value which is greater by one and then the value which is less by one than the setting.

alexpott’s picture

The test changes look good good.

+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/NumericItemBase.php
@@ -78,9 +78,10 @@ public function getConstraints() {
+    $min = is_numeric($settings['min']) ? $settings['min'] : NULL;
...
-    if (!empty($settings['min'])) {

I pondered whether this might introduce php errors if min is not set. It would, but these keys are added in \Drupal\Core\Field\Plugin\Field\FieldType\NumericItemBase::defaultFieldSettings() so they have to be set. So all good here as far as I can see.

drugan’s picture

@alexpott

What I've found on number field settings is the following:

The FieldItemBase::defaultSettings() return is empty array. The NumericitemBase class merges its default min and max settings which are empty strings with FieldItemBase::defaultSettings(). The IntegerItem class merges its default min and max which are empty strings with NumericitemBase::defaultSettings(). Therefore min and max double ensured they exist and have an empty string value by default.

The NumericItemBase->getSettings() return is the result of the following merge:

FieldItemBase::defaultSettings() + NumericitemBase::defaultSettings() + IntegerItem::defaultSettings() + IntegerItem::defaultStorageSettings() + actual field settings and field storage settings saved on the UI form.

So, if the field is created with all default values it has min and max values as NULL.

Why is not empty string?

This is because Number::validateNumber() will not even try to validate the empty string which further goes to FormElement::valueCallback() to be returned as NULL, disregarding of whatever the $input value is. That's how default min and max string values become NULL values after the field being saved empty on a form.

The conclusion of the above is:

The min and max settings are always exist in the NumericItemBase->getSettings() return either as empty strings (in code) or NULL values or actual numeric values saved on those fields.

drugan’s picture

The #26 fix is merged into the latest patch of:

#2816859: Allow the 'step' to be configured as a NumberWidget setting

So, if you want to get more than just properly working constraints on the number filed, please take the patch in the link for testing.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Needs review » Postponed (maintainer needs more info)
Issue tags: +Needs Review Queue Initiative

This issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.

Could someone confirm #3090629: NumericItemBase never sets minimum value constraint if minimum value is zero. solves this issue?

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quietone’s picture

Status: Postponed (maintainer needs more info) » Closed (duplicate)
Issue tags: +Bug Smash Initiative
Related issues: +#3090629: NumericItemBase never sets minimum value constraint if minimum value is zero.

The issue summary at #3090629: NumericItemBase never sets minimum value constraint if minimum value is zero. does look like it is solving the same problem and so does the code changes. I doubled checked and tested as well. I am closing this as a duplicate and moving credit over there.

Cheers