Problem/Motivation

Currently you can refer to same entity multiple times in a multivalue reference field. We need to validate this and display an error when user mistakenly duplicated the reference.

Proposed resolution

To add a second option to Reference validators to unvalidate references that were added twice, by adding new constraint for that.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

sdstyles created an issue. See original summary.

sdstyles’s picture

Status: Active » Needs review
FileSize
5.92 KB
sdstyles’s picture

Title: Add duplication constrain » Add duplication constraint
pfrenssen’s picture

Status: Needs review » Needs work

Awesome patch, thanks for posting this!! It's exactly what I am looking for!

I had a quick code review, found some small remarks. I also tried the patch but I didn't see the option to enable this constraint appearing in the field settings. I'll have a closer look at what's causing that.

  1. +++ b/src/Plugin/Validation/Constraint/DuplicateReferenceConstraint.php
    @@ -0,0 +1,26 @@
    +  public $message = 'This entity (%type: %id) is already referenced.';
    

    Since this is a visitor facing error message we should avoid using technical terms. A visitor does not know what entities or references are, and they haven't memorized entity IDs. They are just trying to invite people to the wedding and don't realize they added Auntie Caroline twice.

    Let's make this a bit more friendly like "The 'invite' field should not have any duplicate values".

  2. +++ b/src/Plugin/Validation/Constraint/DuplicateReferenceConstraintValidator.php
    @@ -0,0 +1,77 @@
    +/**
    + * Checks if referenced entities are valid.
    + */
    

    This is not correct, it checks whether there are duplicates, not whether the references are valid.

  3. +++ b/src/Plugin/Validation/Constraint/DuplicateReferenceConstraintValidator.php
    @@ -0,0 +1,77 @@
    +    /** @var DuplicateReferenceConstraint $constraint */
    

    Instead of refining the class of the constraint through an inline type hint, we could also verify that it is of the correct type and throw an exception if it isn't.

  4. +++ b/src/Plugin/Validation/Constraint/DuplicateReferenceConstraintValidator.php
    @@ -0,0 +1,77 @@
    +    if (!isset($value)) {
    +      return;
    +    }
    

    How can $value not be set if it is passed in as an argument?

    It is probably better to use empty() here.

  5. +++ b/src/Plugin/Validation/Constraint/DuplicateReferenceConstraintValidator.php
    @@ -0,0 +1,77 @@
    +    if (!isset($entity) || $entity->isNew()) {
    +      return;
    +    }
    

    Same here, $entity has been set on the preceding line, so !isset($entity) will always return false here.

  6. +++ b/src/Plugin/Validation/Constraint/DuplicateReferenceConstraintValidator.php
    @@ -0,0 +1,77 @@
    +      // '0' or NULL are considered valid empty references.
    +      if (empty($id)) {
    +        continue;
    +      }
    

    This code is duplicated.

pfrenssen’s picture

Had a deeper look, found two more issues.

  1. +++ b/entity_reference_validators.module
    @@ -28,8 +31,7 @@ function entity_reference_validators_form_field_config_edit_form_alter(array &$f
         && $field->getTargetEntityTypeId() === $field->getItemDefinition()->getSetting('target_type')
    

    This is why I'm not seeing the checkbox in the field configuration, I'm referencing a custom Business entity to a User entity, in my case the two types are not the same.

  2. +++ b/src/Plugin/Validation/Constraint/DuplicateReferenceConstraintValidator.php
    @@ -0,0 +1,77 @@
    +    $entity = $value->getEntity();
    +    if (!isset($entity) || $entity->isNew()) {
    +      return;
    +    }
    

    This part is causing the duplicate check to be skipped when I'm adding a new entity that has a duplicate reference.

pfrenssen’s picture

Status: Needs work » Needs review
FileSize
5.89 KB
6.6 KB

Went ahead and addressed my remarks.

pfrenssen’s picture

Issue tags: +Needs tests

This is also missing some test coverage.

Status: Needs review » Needs work

The last submitted patch, 6: 2825868-6.patch, failed testing.

LOBsTerr’s picture

FileSize
5.93 KB
722 bytes

I have found a small bug. When for the entity reference field we have setting "Create referenced entities if they don't already exist" enabled. We will face the next warning, which breaks the ajax:

Warning: array_count_values(): Can only count STRING and INTEGER values! in /web/modules/contrib/entity_reference_validators/src/Plugin/Validation/Constraint/DuplicateReferenceConstraintValidator.php on line 23

It happens, because the entity doesn't exist yet and as result target_id is empty.
I wanted to check these values for duplications also, but then I realized that this validation checks only existing entities. If we want avoid duplications when we create entities, we should use other solution like this https://www.drupal.org/project/taxonomy_unique. In addition each entity use different fields to search entity.
I have added a fix which filter out the empty values.

LOBsTerr’s picture

FileSize
10.55 KB
15.57 KB

@pfrenssen I have added the functional and kernel tests. Some of the test for CircularReferenceConstraintValidator were broken. I fixed them also. Can you please check it ?

LOBsTerr’s picture

Status: Needs work » Needs review
markdc’s picture

I'm looking for this feature in a multi-value Paragraph that contains a reference field. Since that would involve integrating with the Paragraphs module, I'll create a new feature request.

markdc’s picture

By the way, I tested the above patch and it works. When trying to add the same entity more than once I get a validation error that said entity "has been entered multiple times."

Suggested change: the option should only appear on reference fields set to limit of 2 or more values. It's currently showing on 1-value fields.

alexpott’s picture

Status: Needs review » Reviewed & tested by the community
diff --git a/entity_reference_validators.module b/entity_reference_validators.module
index 5cb07cd..956bca6 100644
--- a/entity_reference_validators.module
+++ b/entity_reference_validators.module
@@ -40,16 +40,15 @@ function entity_reference_validators_form_field_config_edit_form_alter(array &$f
       '#open' => TRUE,
     ];
 
-    // Only check for circular references when the target type is the same as
-    // the field's.
-    if ($field->getTargetEntityTypeId() === $field->getItemDefinition()->getSetting('target_type')) {
-      $form['third_party_settings']['entity_reference_validators']['container']['circular_reference'] = [
-        '#type' => 'checkbox',
-        '#title' => t('Prevent entity from referencing itself'),
-        '#default_value' => $field->getThirdPartySetting('entity_reference_validators', 'circular_reference', FALSE),
-        '#parents' => ['third_party_settings', 'entity_reference_validators', 'circular_reference'],
-      ];
-    }
+    $form['third_party_settings']['entity_reference_validators']['container']['circular_reference'] = [
+      '#type' => 'checkbox',
+      '#title' => t('Prevent entity from referencing itself'),
+      '#default_value' => $field->getThirdPartySetting('entity_reference_validators', 'circular_reference', FALSE),
+      '#parents' => ['third_party_settings', 'entity_reference_validators', 'circular_reference'],
+      // Only check for circular references when the target type is the same as
+      // the field's.
+      '#access' => $field->getTargetEntityTypeId() === $field->getItemDefinition()->getSetting('target_type'),
+    ];
 
     $form['third_party_settings']['entity_reference_validators']['container']['duplicate_reference'] = [
       '#type' => 'checkbox',

We can improve this code by using #access instead of the if.

@fatmarker that's good idea. Let's do that in a follow-up.

  • alexpott committed f5dc0d1 on 8.x-1.x authored by LOBsTerr
    Issue #2825868 by LOBsTerr, pfrenssen, sdstyles, fatmarker, alexpott:...
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Status: Fixed » Closed (fixed)

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

Francewhoa’s picture

Thanks all for your contributions with this port :)

Today I set the related ticket to fixed