The \Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator works with only single value field and therefore it cannot be used with fields that have multiple values, like string lists.

On line #34 the condition is:

$value_taken = (bool) \Drupal::entityQuery($entity_type_id)
      // The id could be NULL, so we cast it to 0 in that case.
      ->condition($id_key, (int) $items->getEntity()->id(), '<>')
      ->condition($field_name, db_like($items->first()->value), 'LIKE')
      ->range(0, 1)
      ->count()
      ->execute();

instead it should be:

$values = array();
foreach ($items->getIterator() AS $item) {
  // The original code uses $item->value and although I prefer $item->getValue()
  // we don't know what the key for the field will be so using 'value' is ok.
  $values[] = $item->value;
}

$value_taken = (bool) \Drupal::entityQuery($entity_type_id)
      // The id could be NULL, so we cast it to 0 in that case.
      ->condition($id_key, (int) $items->getEntity()->id(), '<>')
      ->condition($field_name, $values, 'IN')
     // Or ->condition($field_name, ':values[]', 'IN', array(':values[]' => $values))
     // I'm not sure what the current approach is.
      ->range(0, 1)
      ->count()
      ->execute();

Steps to reproduce

Add the UniqueField constraint to a field by putting the following hook into a custom .module file:

/**
 * Implements hook_entity_bundle_field_info_alter().
 */
function MYMODULE_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
  if ($bundle === 'YOUR_BUNDLE' && !empty($fields['field_YOUR_FIELD'])) {
    $fields['field_YOUR_FIELD']->addConstraint('UniqueField', []);
  }
}

If your field is multi-valued the validation will not work past the first delta. For example, if you have a multi-value plaintext field and you repeatedly input "aaa" then the constraint will allow it. This is also the case across different entities; it will check the first delta on each entity but not beyond it.

Additionally, per issue 2973455 the validator also will not validate entity reference fields or compound fields such as links.

CommentFileSizeAuthor
#87 2478663-nr-bot.txt91 bytesneeds-review-queue-bot
#85 interdiff-10.1.x-84-85.txt2.28 KBcaesius
#85 interdiff-10.0.x-84-85.txt1.79 KBcaesius
#85 interdiff-9.5.x-84-85.txt1.06 KBcaesius
#85 2478663-10.1.x-85.patch16.6 KBcaesius
#85 2478663-10.0.x-85.patch16.74 KBcaesius
#85 2478663-9.5.x-85.patch16.72 KBcaesius
#84 2478663-10.1.x-84.patch16.76 KBcaesius
#84 2478663-9.5.x-10.0.x-84.patch16.83 KBcaesius
#83 2478663-nr-bot.txt147 bytesneeds-review-queue-bot
#79 interdiff-2478663-78-79.txt4.03 KBcaesius
#79 interdiff-2478663-63-79.txt16.41 KBcaesius
#79 2478663-9x-79.patch16.84 KBcaesius
#78 interdiff-2478663-77-78.txt6.08 KBcaesius
#78 interdiff-2478663-63-77.txt13.85 KBcaesius
#78 2478663-9x-78.patch14.3 KBcaesius
#77 2478663-9x-77-newdev.patch12.72 KBcaesius
#77 interdiff-2478663-76-77.txt2.15 KBcaesius
#77 interdiff-2478663-63-77.txt11.81 KBcaesius
#4 drupal-UniqueFieldValueValidator-works-only_with_single_value_fields-2478663-3-D8.patch1.05 KBsagar ramgade
#7 2478663-7.patch1.04 KBsanja_m
#7 interdiff.txt793 bytessanja_m
#9 2478663-9.patch1.04 KBsanja_m
#9 interdiff.txt781 bytessanja_m
#11 2478663-11.patch1.03 KBsanja_m
#11 interdiff.txt765 bytessanja_m
#12 2478663-12.patch5.04 KBsanja_m
#12 interdiff-2478663-12.txt4.01 KBsanja_m
#16 2478663-16.patch4.88 KBsanja_m
#16 interdiff-2478663-16.txt2.48 KBsanja_m
#18 2478663-18.patch5.61 KBsanja_m
#18 interdiff-2478663-18.txt3.51 KBsanja_m
#20 2478663-20-test-only.patch5.61 KBsanja_m
#20 2478663-20.patch5.61 KBsanja_m
#21 2478663-21-test-only.patch4.58 KBsanja_m
#25 2478663_25.patch5.21 KBslashrsm
#25 interdiff.txt1.22 KBslashrsm
#25 2478663_25-TEST-ONLY.patch4.18 KBslashrsm
#33 2478663_33.patch5.21 KBslashrsm
#45 2478663-45.patch3.14 KBalphawebgroup
#48 2478663-48.patch11.5 KBalphawebgroup
#52 2478663-52.patch11.91 KBalphawebgroup
#59 2478663-9x-59.patch12.05 KBTraverus
#60 2478663-9x-60.patch12.92 KBdrews_man
#63 2478663-9x-63.patch12.78 KBdrews_man
#67 1636757201.png52.02 KBcaesius
#68 2478663-9x-68.patch13.34 KBcaesius
#68 interdiff-2478663-63-68.txt3.73 KBcaesius
#69 interdiff-2478663-63-69.txt3.73 KBcaesius
#69 2478663-9x-69.patch13.34 KBcaesius
#70 interdiff-2478663-69-70.txt794 bytescaesius
#70 2478663-9x-70.patch13.38 KBcaesius
#71 2478663-9x-71.patch13.47 KBcaesius
#71 interdiff-2478663-70-71.txt1.18 KBcaesius
#72 2478663-9x-72.patch12.97 KBcaesius
#72 interdiff-2478663-71-72.txt4.88 KBcaesius
#73 interdiff-2478663-71-72.txt9.35 KBcaesius
#74 2478663-9x-74.patch13.06 KBcaesius
#74 interdiff-2478663-63-74.txt11.7 KBcaesius
#74 interdiff-2478663-72-74.txt1.4 KBcaesius
#76 2478663-9x-76.patch13.01 KBcaesius
#76 interdiff-2478663-63-76.txt12.1 KBcaesius
#76 interdiff-2478663-74-76.txt818 bytescaesius

Issue fork drupal-2478663

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

upchuk’s picture

I agree. It doesn't make any sense if it can only check the first item in the list..

sanja_m’s picture

Assigned: Unassigned » sanja_m
sagar ramgade’s picture

Status: Active » Needs review

A quick patch attached :-)

sagar ramgade’s picture

Status: Needs review » Needs work
Anonymous’s picture

Since this can be applied to strings only I think it should be just $items->getValue() which should return array of values.

sanja_m’s picture

Status: Needs work » Needs review
StatusFileSize
new1.04 KB
new793 bytes

There was an error in one line.

Status: Needs review » Needs work

The last submitted patch, 7: 2478663-7.patch, failed testing.

sanja_m’s picture

Status: Needs work » Needs review
StatusFileSize
new1.04 KB
new781 bytes

One typo error fixed.

slashrsm’s picture

Status: Needs review » Needs work

Great! One comment:

+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
@@ -29,10 +29,14 @@ public function validate($items, Constraint $constraint) {
+    foreach ($items->getIterator() as $item) {

You should be able to do only:

forach ($items as $item)

And it would be great to add test coverage.

sanja_m’s picture

Status: Needs work » Needs review
StatusFileSize
new1.03 KB
new765 bytes

Removed get->Iterator(), still working on tests.

sanja_m’s picture

StatusFileSize
new5.04 KB
new4.01 KB

Still working on this patch.

Status: Needs review » Needs work

The last submitted patch, 12: 2478663-12.patch, failed testing.

sanja_m’s picture

I need some guidelines regarding drupalPostForm(), when I add first values for testing there is an exception thrown, and I can not figure out what the problem is exactly.

slashrsm’s picture

  1. +++ b/core/modules/system/src/Tests/Validation/UniqueValuesConstraintValidatorTest.php
    @@ -0,0 +1,55 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  protected function setUp() {
    +    parent::setUp();
    +  }
    +
    

    No need to implement setUp() with an empty body. Simply remove it here and parent's implementation will be used.

  2. +++ b/core/modules/system/src/Tests/Validation/UniqueValuesConstraintValidatorTest.php
    @@ -0,0 +1,55 @@
    +    $entity = EntityTestUniqueConstraint::create(['type' => 'foo']);
    

    "foo" type doesn't exist for EntityTestUniqueConstraint entity. This is one of the reasons for fatals you are seeing.

    The nicest solution would be to remove "type" from base fields definition entirely and pass empty value here.

    You can also set field_test_text here if needed.

  3. +++ b/core/modules/system/src/Tests/Validation/UniqueValuesConstraintValidatorTest.php
    @@ -0,0 +1,55 @@
    +    $entity = EntityTestUniqueConstraint::create(['type' => 'foo']);
    
    +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUniqueConstraint.php
    @@ -0,0 +1,69 @@
    + *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
    + *     "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
    + *     "form" = {
    + *       "default" = "Drupal\entity_test\EntityTestForm"
    + *     },
    

    This classes don't exists. You can most likely use base classes provided by core instead.

  4. +++ b/core/modules/system/src/Tests/Validation/UniqueValuesConstraintValidatorTest.php
    @@ -0,0 +1,55 @@
    +    $this->drupalPostForm('entity_test_unique_constraint/manage/' . $entity->id() . '/edit', $edit, t('Save'));
    

    Another fatal here. You are trying to access a route that is not defined. You basically have two solutions here:
    - define route for this URL or
    - test constraint programatically (update fields and save instead of post to form).

    Constraints don't require forms to work. Even if you manipulate entities programatically they make sure your data is correct.

sanja_m’s picture

Status: Needs work » Needs review
StatusFileSize
new4.88 KB
new2.48 KB

#15.1 - fixed.
#15.2 and #15.4 - still working.
#15.3 - I found those classes with the same namespaces I use in my code, so now I'm not sure do I need to change them or not?

Status: Needs review » Needs work

The last submitted patch, 16: 2478663-16.patch, failed testing.

sanja_m’s picture

Status: Needs work » Needs review
StatusFileSize
new5.61 KB
new3.51 KB

Finished all the issues and tested locally, needs review.

Status: Needs review » Needs work

The last submitted patch, 18: 2478663-18.patch, failed testing.

sanja_m’s picture

Status: Needs work » Needs review
StatusFileSize
new5.61 KB
new5.61 KB

Test with fix patch.

sanja_m’s picture

StatusFileSize
new4.58 KB

Test only patch.

The last submitted patch, 20: 2478663-20-test-only.patch, failed testing.

The last submitted patch, 20: 2478663-20.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 21: 2478663-21-test-only.patch, failed testing.

slashrsm’s picture

Status: Needs work » Needs review
StatusFileSize
new5.21 KB
new1.22 KB
new4.18 KB

This should fix the last strange test fail.

Status: Needs review » Needs work

The last submitted patch, 25: 2478663_25-TEST-ONLY.patch, failed testing.

pivica’s picture

Assigned: sanja_m » Unassigned
Status: Needs work » Reviewed & tested by the community

The last submitted patch, 21: 2478663-21-test-only.patch, failed testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 25: 2478663_25-TEST-ONLY.patch, failed testing.

slashrsm’s picture

Status: Needs work » Reviewed & tested by the community

The last submitted patch, 21: 2478663-21-test-only.patch, failed testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 25: 2478663_25-TEST-ONLY.patch, failed testing.

slashrsm’s picture

Status: Needs work » Needs review
StatusFileSize
new5.21 KB

This is identical to #25.

pivica’s picture

Status: Needs review » Reviewed & tested by the community

And RTBC one more time.

pivica’s picture

The last submitted patch, 21: 2478663-21-test-only.patch, failed testing.

The last submitted patch, 25: 2478663_25-TEST-ONLY.patch, failed testing.

berdir’s picture

  1. +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
    @@ -29,10 +29,14 @@ public function validate($items, Constraint $constraint) {
    +    $values = array();
    +    foreach ($items as $item) {
    +      $values[] = $item->value;
    +    }
    

    Hardcoding ->value is problematic but this issue doesn't make it worse than it already is.

    For example, this won't work on an entity reference field, since the main property is target_id there, not value. If you want to do it correctly, call getMainPropertyName() from the field storage definition.

    Not sure if we want to fix that here but when we touch the code anyway, we might as well do it correctly? Would have to change the test to use an entity_reference field type to make sure this works.

  2. +++ b/core/modules/system/src/Tests/Validation/UniqueValuesConstraintValidatorTest.php
    @@ -0,0 +1,62 @@
    +    // Add field with two values.
    +    $definition = ['field_test_text' => ['text1', 'text2']];
    +    $entity = EntityTestUniqueConstraint::create($definition);
    ...
    +
    +    // Add another field with invalid values.
    +    $definition = ['field_test_text' => ['text2', 'text1']];
    +    $entity = EntityTestUniqueConstraint::create($definition);
    

    Does this really fail?

    If we use the first value only, then we use text2, but that will match the existing value in the database?

    I would have expected the test to use test5, test1 so that we are sure that the non-first value is really used?

  3. +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUniqueConstraint.php
    @@ -0,0 +1,61 @@
    +/**
    + * Defines a test entity class for unique constraint.
    + *
    + * @ContentEntityType(
    + *   id = "entity_test_unique_constraint",
    + *   label = @Translation("unique field entity"),
    ...
    + *   base_table = "entity_test_unique_constraint",
    + *   data_table = "entity_test_unique_constraint_data",
    

    I'm worried about the amount of entity types we have in entity_test. Every time we enable that module, at least in web test, we have to create dozens of tables for every single one of them. So adding more makes our tests slower.

    Don't have a good alternative though, other than putting it in a separate test module.

    Do we really need a data table for this, though?

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Setting back to needs work so that #38 can be addressed.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.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.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.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.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should 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.

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

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should 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.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should 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.

alphawebgroup’s picture

StatusFileSize
new3.14 KB
alphawebgroup’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 45: 2478663-45.patch, failed testing. View results

alphawebgroup’s picture

Status: Needs work » Needs review
StatusFileSize
new11.5 KB
alphawebgroup’s picture

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

Status: Needs review » Needs work

The last submitted patch, 48: 2478663-48.patch, failed testing. View results

alphawebgroup’s picture

alphawebgroup’s picture

Status: Needs work » Needs review
StatusFileSize
new11.91 KB

Status: Needs review » Needs work

The last submitted patch, 52: 2478663-52.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

claudiu.cristea’s picture

wengerk’s picture

mbovan’s picture

  1. +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
    @@ -17,27 +17,54 @@ class UniqueFieldValueValidator extends ConstraintValidator {
    +        $this->context->addViolation($constraint->message, [
    +          '%value' => $item->value,
    +          '@entity_type' => $entity->getEntityType()->getLowercaseLabel(),
    ...
    +        ]);
    

    I think we should limit this to the specific field delta that does not pass validation instead of setting a validation error on a field. It becomes UX problematic if you use a multi-value field.

    This snippet works fine for me:

            $this->context->buildViolation($constraint->message, [
              '%value' => $item->value,
              '@entity_type' => $entity->getEntityType()->getLowercaseLabel(),
              '@field_name' => mb_strtolower($items->getFieldDefinition()->getLabel()),
            ])->atPath($delta)->addViolation();
    
  2. +++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
    @@ -17,27 +17,54 @@ class UniqueFieldValueValidator extends ConstraintValidator {
    +        break;
    

    Also, I think the validation error becomes more useful if we run it on each delta value so a user can fix all errors at once. Thus, we should remove break.

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

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.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: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should 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.

Traverus’s picture

StatusFileSize
new12.05 KB

Rerolled patched for 9.0.x, also checked to ensure it works against 8.9.x

Also removed the break line.

drews_man’s picture

Version: 8.9.x-dev » 9.3.x-dev
Status: Needs work » Needs review
StatusFileSize
new12.92 KB

Rewrite patches 59 && 52 and rewrite the test for this patch.

drews_man’s picture

Version: 9.3.x-dev » 8.9.x-dev
drews_man’s picture

Assigned: Unassigned » drews_man
Status: Needs review » Active
drews_man’s picture

StatusFileSize
new12.78 KB

Rewrote patch 60 according to Drupal coding standards.

Also, retrieve the break. Since if there are two identical values in entities, there is no point in further validating the type.

drews_man’s picture

Assigned: drews_man » Unassigned
Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 63: 2478663-9x-63.patch, failed testing. View results

berdir’s picture

+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestUniqueConstraint.php
@@ -0,0 +1,60 @@
+    $fields['field_test_reference'] = BaseFieldDefinition::create('entity_reference')
+      ->setLabel(t('unique_reference_test'))
+      ->setCardinality(2)
+      ->addConstraint('UniqueField')
+      ->setSettings([
+        'target_type' => 'node',
+        'handler' => 'default:node',
+        'handler_settings' => [
+          'target_bundles' => [
+            'article',
+          ],

this is breaking a ton of tests. this shouldn't use node as target but some other test entity type.

caesius’s picture

StatusFileSize
new52.02 KB

Patch 63 hoses the ability to edit existing user accounts. On a vanilla sandbox site any attempt to do so results in an error like this one:

    The username admin is already taken.
    The email address admin@example.com is already taken.

screenshot

caesius’s picture

Version: 8.9.x-dev » 9.3.x-dev
StatusFileSize
new13.34 KB
new3.73 KB

New patch. It could probably be improved in terms of coding standards.

I have not tested it against entity reference fields. I'm also completely unfamiliar with writing tests so I haven't touched them.

caesius’s picture

StatusFileSize
new3.73 KB
new13.34 KB

Coding standards...

caesius’s picture

StatusFileSize
new794 bytes
new13.38 KB

Fix undefined variable notice.

caesius’s picture

StatusFileSize
new13.47 KB
new1.18 KB

I think string assertion errors like this one are due to the line ->atPath($delta) which is only necessary on multivalue fields. Updating patch to account for that. Hopefully this run will narrow down the errors to only those about the missing node test entity type.

Edit: That got rid of most of them, except for this one, which comes from tests added by the patch itself. Since we're now passing the delta so that the error can point to a specific item that's problematic rather than highlighting every item in the field, it should be fine to modify the test to assert against the new value.

I think the only other error which is not related to the missing node:article entity is this which seems to indicate that this patch causes string IDs to no longer validate.

caesius’s picture

StatusFileSize
new12.97 KB
new4.88 KB

I locally ran a bunch of the tests that failed to hammer this out. Big interdiff incoming.

caesius’s picture

StatusFileSize
new9.35 KB

Wrong interdiff.

caesius’s picture

StatusFileSize
new13.06 KB
new11.7 KB
new1.4 KB

I haven't been able to get functional tests running locally, so not sure how to debug PrepareUninstallTest -- although it might be fixed if I update the dependencies to remove drupal:node. Also, the extra error on 9.2.x might be fixed by adding drupal:user as a dependency. The jsonapi error has me stumped.

Adding a @todo referencing 3029782 since that's marked RTBC and this patch will need to be rerolled to incorporate it if that gets in first.

Note that this patch addresses 2973455 and https://www.drupal.org/project/drupal/issues/2999225

I'm including an interdiff for #63 as well.

caesius’s picture

Status: Needs work » Needs review
caesius’s picture

StatusFileSize
new818 bytes
new12.1 KB
new13.01 KB

Updating the patch to remove a few references to node that I missed.

A few questions for any reviewer that comes along who happens to know their stuff:

1. Would it be possible to get this backported to 9.2.x, and if so, how do I go about fixing this strange database failure? (assuming this updated patch doesn't fix it)

2. Is there a "more correct," futureproof, Drupal-y way to perform this check for ->getCardinality()?:

    // Determine the main property name of the field what we will check.
    $field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
    $property_name = $field_storage_definitions[$field_name]->getMainPropertyName();
    if (get_class($field_storage_definitions[$field_name]) == 'Drupal\Core\Field\BaseFieldDefinition') {
      $cardinality = $field_storage_definitions[$field_name]->getFieldStorageDefinition()->getCardinality();
    }
    else {
      $cardinality = $field_storage_definitions[$field_name]->getCardinality();
    }

The reason this if/else is needed is because some fields, for example username and email on a User entity, will have the class Drupal\Core\Field\BaseFieldDefinition, while other fields -- such as a field added to an entity through the UI -- have the class Drupal\field\Entity\FieldStorageConfig. This is touched on a bit in docs for getFieldStorageDefinitions which notes that the former is a "base field" while the latter is a "bundle field."

caesius’s picture

StatusFileSize
new11.81 KB
new2.15 KB
new12.72 KB

Simplify cardinality/multivalue check (addresses question 2 above).

9.2.x is still failing for some reason but the patch should be good to apply to 9.2 sites if it's needed.

caesius’s picture

StatusFileSize
new14.3 KB
new13.85 KB
new6.08 KB

Previous patches did not prevent entities from having multiple identical values within a field upon initial creation. Checking the current entity's field values does not require a database query; it should only require checking the $items that have been passed. Also added tests for this scenario.

edit: Interdiff filename should be 63-78 not 63-77

caesius’s picture

StatusFileSize
new16.84 KB
new16.41 KB
new4.03 KB

Previous patches stopped validating after encountering a single violation. The UI will probably stop multiple violations from occurring when entering data into a single multivalue field, but it's definitely possible when working programmatically. It's probably a good idea to report all violations. If that's not the case and we only want to report one violation at a time then patch 78 is fine.

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.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new147 bytes

The Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

caesius’s picture

StatusFileSize
new16.83 KB
new16.76 KB

Rerolling...

caesius’s picture

StatusFileSize
new16.72 KB
new16.74 KB
new16.6 KB
new1.06 KB
new1.79 KB
new2.28 KB

Addressing issues that came up in tests.

caesius’s picture

Status: Needs work » Needs review
needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new91 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

caesius’s picture

Status: Needs work » Needs review
Issue tags: +no-needs-review-bot

I'm not sure how to get the bot to not test the 9.5.x patch against 10.1.x, so not gonna fight with it.

If someone tries to review this and finds that any of the patches really do not apply, then poke me.

caesius’s picture

caesius’s picture

Issue tags: -no-needs-review-bot

I've converted the 10.1.x patch to an MR. Hopefully the NR bot checks that instead of the 9.5 and 10.0 patch files.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs Review Queue Initiative

Verified the issue by using the snippet in the issue summary.
Added it to the node.module for testing
Used the Article content type from the standard install
Added a simple test filed, set to unlimited
Created an Article and added Test to both fields
Saved fine

Applied patch
Edited node
Tried saving again
Validation error was thrown, as expected.

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.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

Looking good, left some comments on the MR, thanks!

caesius’s picture

Status: Needs work » Needs review

I addressed all MR threads to the best of my ability.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Appears all of @larowlans threads have been resolved.

caesius’s picture

Status: Reviewed & tested by the community » Needs review

Hate to send my own work back to needs review, but I mulled over one of larowlans suggestions and decided to properly implement it to reduce redundant looping during the "own duplicate value" check. See commit e71fcaca

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Update looks good to me. Lets move to committers queue.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

Looking good, left some comments on the MR about coding standards (minor) and one curly one about the update path here for sites with invalid data.

I'll ask the release managers what their thoughts are with respect to that.

larowlan’s picture

I've asked release managers which of these options is the best

a) Do nothing, users will need to fix invalid duplicates next time they save content
b) Some sort of report for users - this will have performance issues - to generate it we'd probably need a background process
c) Some sort of update hook - this could cause data loss issues so feels risky

Discussed with @lauriii and he agreed a) felt best, but we'd also need a change record and release note snippet (as we'd want to highlight this in release notes).

Tagging as such

I'll wait for a release manager to confirm a) is the best approach and report back either way

caesius’s picture

Updated the MR for coding standards.

My personal opinion regarding this change and its implications for site content editors is that the original functionality for this constraint was not intended to work with multi-valued fields. The only way for this constraint to apply to multi-valued fields would be for a developer to write a module that adds the constraint to the field. I don't believe this constraint is used in core in any way on multi-valued fields, and I can't imagine someone hacking the core user email field to be multi-valued and then using this constraint -- but if they have, they've probably already found it to be broken and applied one of the above patches.

Describing the issue in the change record and noting the situations in which content editing would be adversely affected necessarily requires pointing out that this constraint is being used in a way it wasn't intended, resulting in either not achieving the desired result in the first place or supporting a very specific use case. For the latter, we should instruct developers to write and implement their own constraint that mimics the old functionality.

Edit: It might be better to say that the method "was not supported" rather then "not intended." The original code was simply written too narrowly, supporting only single-valued fields with value as the main property.

larowlan credited catch.

larowlan credited lauriii.

larowlan’s picture

Issue summary: View changes
Status: Needs work » Needs review

I discussed with @catch who also agreed that a) is the best approach

I added a draft change record and release note snippet

This is ready for review again

caesius’s picture

Change record also needs to mention that the validator will now also work with additional field types, such as Entity Reference fields.

This sentence is also not 100% accurate:

Prior to 10.2.0 the validator only supported single-value fields and would not detect duplicates for fields with more than one value.

It would actually check for duplicates, but only in the first entry (delta) for each field. If someone really wanted to enter duplicate values they could "work around" the constraint by simply making sure the first value is different. It's feasible this unsupported behavior could have been used to hack together functionality where the first value in a field would be a "key." So the CR should mention that if the original behavior was deliberately used in this way then a dev would need to write a new constraint themselves.

This is how I think it should read:

The UniqueFieldValueValidator provides a constraint that can be added to a field to ensure that all values in the field are unique to that entity.

Drupal core uses this constraint for the User's name and mail fields.

Prior to 10.2.0 the validator only supported single-value fields and would not detect all duplicates for fields with more than one value. The first entry in the field would be validated against the first entry in all other entities, but subsequent entries would not be validated for uniqueness, whether against other entities or within the entity itself. Additionally, only simple field types such as plaintext fields would be validated, while entity reference and composite fields would not.

From 10.2.0 the validator will mark entities with duplicates in multi-value fields as invalid, regardless of whether the duplicates occur in other entities or within the validating entity. Additionally, the field's main property will be used for validation, thus providing validation for e.g. Link fields whose main property is the Link URL.

Future edits of content implementing UniqueFieldValueValidator will require removing duplicate values.

If the original behavior of the validator was used deliberately, e.g. for designating only the first value in a multivalue field as a "key," then the constraint will need to be removed from the field and a new custom constraint implemented. See Defining Constraints for more information.

larowlan’s picture

@caesius thanks - can you make those edits?

caesius’s picture

Didn't know I could do that -- done!

larowlan’s picture

Thanks 🙌

larowlan’s picture

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Remarking as CR seems to be updated.

larowlan’s picture

Issue summary: View changes

Hiding patches as there's an MR

Updating credits

  • larowlan committed 3981c8aa on 11.x
    Issue #2478663 by caesius, sanja_m, slashrsm, alphawebgroup, Drews_man,...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed 3981c8a and pushed to 11.x. Thanks!

larowlan’s picture

Fixed on commit too, ran the tests locally

--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/UniqueFieldValueValidator.php
@@ -14,31 +14,15 @@
  */
 class UniqueFieldValueValidator extends ConstraintValidator implements ContainerInjectionInterface {
 
-  /**
-   * The entity field manager.
-   *
-   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
-   */
-  protected $entityFieldManager;
-
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
   /**
    * Creates a UniqueFieldValueValidator object.
    *
-   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
+   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
    *   The entity type manager.
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
    *   The entity type manager.
    */
-  public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager) {
-    $this->entityFieldManager = $entity_field_manager;
-    $this->entityTypeManager = $entity_type_manager;
+  public function __construct(protected EntityFieldManagerInterface $entityFieldManager, protected EntityTypeManagerInterface $entityTypeManager) {
   }
 
   /**


Tests output

phpunit -c app/core app/core/tests/Drupal/KernelTests/Core/Validation/UniqueValuesConstraintValidatorTest.php
⏳️ Bootstrapped tests in 0 seconds.
🐘 PHP Version 8.1.16.
💧 Drupal Version 11.0-dev.
🗄️  Database engine mysql.
PHPUnit 9.6.10 by Sebastian Bergmann and contributors.

Testing Drupal\KernelTests\Core\Validation\UniqueValuesConstraintValidatorTest
....                                                                4 / 4 (100%)

Time: 00:03.177, Memory: 10.00 MB

OK (4 tests, 74 assertions)


caesius’s picture

I believe this fully addresses all related/referenced issues as well (some of which are functionally duplicates of each other)

I think the only thing left to do may be to write additional tests to more explicitly test e.g. Link fields and fields with custom properties. #3291483 looks like the best fit for that if we feel more tests are needed to prevent regressions.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

smustgrave credited AndyF.

smustgrave credited RoSk0.

cilefen’s picture

nico.b’s picture

I think this might have caused another (very similar) regression: https://www.drupal.org/project/drupal/issues/3456964