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.
| Comment | File | Size | Author |
|---|
Issue fork drupal-2478663
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:
- 2478663-uniquefieldvaluevalidator-works-only
changes, plain diff MR !3811
Comments
Comment #1
upchuk commentedI agree. It doesn't make any sense if it can only check the first item in the list..
Comment #2
sanja_m commentedComment #3
sagar ramgade commentedA quick patch attached :-)
Comment #4
sagar ramgade commentedComment #6
Anonymous (not verified) commentedSince this can be applied to strings only I think it should be just $items->getValue() which should return array of values.
Comment #7
sanja_m commentedThere was an error in one line.
Comment #9
sanja_m commentedOne typo error fixed.
Comment #10
slashrsm commentedGreat! One comment:
You should be able to do only:
And it would be great to add test coverage.
Comment #11
sanja_m commentedRemoved get->Iterator(), still working on tests.
Comment #12
sanja_m commentedStill working on this patch.
Comment #14
sanja_m commentedI 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.
Comment #15
slashrsm commentedNo need to implement setUp() with an empty body. Simply remove it here and parent's implementation will be used.
"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.
This classes don't exists. You can most likely use base classes provided by core instead.
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.
Comment #16
sanja_m commented#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?
Comment #18
sanja_m commentedFinished all the issues and tested locally, needs review.
Comment #20
sanja_m commentedTest with fix patch.
Comment #21
sanja_m commentedTest only patch.
Comment #25
slashrsm commentedThis should fix the last strange test fail.
Comment #27
pivica commentedComment #30
slashrsm commentedComment #33
slashrsm commentedThis is identical to #25.
Comment #34
pivica commentedAnd RTBC one more time.
Comment #35
pivica commentedComment #38
berdirHardcoding ->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.
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?
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?
Comment #39
alexpottSetting back to needs work so that #38 can be addressed.
Comment #45
alphawebgroupComment #46
alphawebgroupComment #48
alphawebgroupComment #49
alphawebgroupComment #51
alphawebgroupComment #52
alphawebgroupComment #54
claudiu.cristeaAdded #3027745: UniqueFieldConstraint doesn't work for entities with string IDs as related issue.
Comment #55
wengerkAdd #2999225: Unique field constraint does not validated field values on delta greater than 0 as related issue.
It may seems to be a duplicate nop ?
I see that #2999225: Unique field constraint does not validated field values on delta greater than 0 has a working patch, what should we do ?
Comment #56
mbovan commentedI 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:
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.Comment #59
Traverus commentedRerolled patched for 9.0.x, also checked to ensure it works against 8.9.x
Also removed the break line.
Comment #60
drews_manRewrite patches 59 && 52 and rewrite the test for this patch.
Comment #61
drews_manComment #62
drews_manComment #63
drews_manRewrote 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.
Comment #64
drews_manComment #66
berdirthis is breaking a ton of tests. this shouldn't use node as target but some other test entity type.
Comment #67
caesius commentedPatch 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:
Comment #68
caesius commentedNew 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.
Comment #69
caesius commentedCoding standards...
Comment #70
caesius commentedFix undefined variable notice.
Comment #71
caesius commentedI 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 missingnodetest 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:articleentity is this which seems to indicate that this patch causes string IDs to no longer validate.Comment #72
caesius commentedI locally ran a bunch of the tests that failed to hammer this out. Big interdiff incoming.
Comment #73
caesius commentedWrong interdiff.
Comment #74
caesius commentedI 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 removedrupal:node. Also, the extra error on 9.2.x might be fixed by addingdrupal:useras a dependency. Thejsonapierror 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.
Comment #75
caesius commentedComment #76
caesius commentedUpdating the patch to remove a few references to
nodethat 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()?: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 classDrupal\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."Comment #77
caesius commentedSimplify 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.
Comment #78
caesius commentedPrevious 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
Comment #79
caesius commentedPrevious 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.
Comment #83
needs-review-queue-bot commentedThe 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.
Comment #84
caesius commentedRerolling...
Comment #85
caesius commentedAddressing issues that came up in tests.
Comment #86
caesius commentedComment #87
needs-review-queue-bot commentedThe 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.
Comment #88
caesius commentedI'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.
Comment #89
caesius commentedComment #91
caesius commentedI'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.
Comment #92
caesius commentedComment #93
smustgrave commentedVerified 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.
Comment #95
larowlanLooking good, left some comments on the MR, thanks!
Comment #96
caesius commentedI addressed all MR threads to the best of my ability.
Comment #97
smustgrave commentedAppears all of @larowlans threads have been resolved.
Comment #98
caesius commentedHate 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
Comment #99
smustgrave commentedUpdate looks good to me. Lets move to committers queue.
Comment #100
larowlanLooking 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.
Comment #101
larowlanI'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
Comment #102
caesius commentedUpdated 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
valueas the main property.Comment #105
larowlanI 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
Comment #106
caesius commentedChange 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:
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:
Comment #107
larowlan@caesius thanks - can you make those edits?
Comment #108
caesius commentedDidn't know I could do that -- done!
Comment #109
larowlanThanks 🙌
Comment #110
larowlanComment #111
smustgrave commentedRemarking as CR seems to be updated.
Comment #112
larowlanHiding patches as there's an MR
Updating credits
Comment #114
larowlanCommitted 3981c8a and pushed to 11.x. Thanks!
Comment #115
larowlanFixed on commit too, ran the tests locally
Tests output
Comment #117
caesius commentedI 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.
Comment #126
smustgrave commentedMoving over credit from #2973455: Unique field constraint does not work with any field main property name that is not "value"
Comment #127
cilefen commentedThis may have caused regressions.
Comment #128
cilefen commentedAlso #3419816: Add support for case sensitive validation to UniqueFieldValuesConstraint.
Comment #129
nico.b commentedI think this might have caused another (very similar) regression: https://www.drupal.org/project/drupal/issues/3456964
Comment #130
nico.b commented