Problem
The User module has a UserUniqueValidator whose job is to validate user fields like name and email for uniqueness. To do this, its validate() method must access contextual data other than the raw $value that is passed in. It currently does this with the following nasty code:
$field = $this->context->getMetadata()->getTypedData()->getParent();
Proposed resolution
Accessing the right data value is currently hard, as the constraints are put on the wrong layer. Instead of putting constraints on field item properties, we put them on the whole field. That way, the field is passed as value to the constraint validator and no further, complicated calls are required to get the right data.
Remaining tasks
-
User interface changes
-
API changes
-
Suggested commit message Issue #2110345 by Désiré, mr.york, fgm, fago, stefan.r, effulgentsia: Improve DX of validation constraints for fields.
Original report by effulgentsia
The User module has a UserUniqueValidator whose job is to validate user fields like name and email for uniqueness. To do this, its validate() method must access contextual data other than the raw $value that is passed in. It currently does this with the following nasty code:
$field = $this->context->getMetadata()->getTypedData()->getParent();
That's exposing way too much implementational detail of how Symfony's validation contexts, metadata for those contexts, and Drupal's TypedData systems are all stitched together. Instead, let's provide a base class with a protected getFieldItem() method to hide that detail from specific field validators.
#2002158: Convert form validation of comments to entity validation contains another use case for this, and surely, more will surface in core and contrib as the remaining entity validation tasks are worked through.
| Comment | File | Size | Author |
|---|---|---|---|
| #86 | drupal8-validation_constraints_for_fields-2110345-86.patch | 8.82 KB | effulgentsia |
| #81 | interdiff.txt | 2.41 KB | effulgentsia |
| #81 | drupal8-validation_constraints_for_fields-2110345-81.patch | 10.18 KB | effulgentsia |
| #79 | drupal8-validation_constraints_for_fields-2110345-79.patch | 10.17 KB | effulgentsia |
| #73 | interdiff.txt | 1.67 KB | pfrenssen |
Comments
Comment #1
amateescu commentedYep, this makes a lot of sense :)
The one line description must be updated.
I'm not sure about this one, should exceptions include the fully qualified class name?
Comment #2
moshe weitzman commentedHiding details is good and this patch should continue on. But I don't consider this a complete fix to the problem. This code is a turd, and a turd hiding class like this one isn't a full resolution.
Comment #3
webchick+1 subscribe.
Comment #4
fagoTrue, but I think the DX problem is more caused by the validation constraints being not implement for field items, but field item properties. When the constraint actually deals with the field, it should be implemented on field level. If it deals just with a single value without any additional context, i.e. to check a regex, it fits well on the individual field property value.
Attached patch shows how the user constraint would be implemented on field item level - imho it's not that bad. You just get the field item object passed as value, what should be just fine. The only gotcha I see is that the field item might be NULL if it's empty, but that's the case for all constraint plugins and doing a
in the beginning is what 90% of the symfony constraint validators already do also. (It's mostly NULL, Blank constraints that deal with the NULL value then - others should ignore them.)
In regard to the EntityChangedConstraint, I'm not sure we even need that. I'd see EntityChanged to be its own entity field type (not configurable), which
a) can care about setting/update the value as appropriately
b) can implement getConstraint() to return a constraint on the field, comparing it with last changed that of the entity. Thus, we could configure a regular compare-date constraint here and do not even need our own plugin.
Howsoever, that would totally deserve its own issue.
Comment #5
klausiThat patch makes a lot of sense, I totally agree that we should operate on the field item level for such context-sensitive constraints.
Comment #6
chx commentedI stiill see a
+ $field = $field_item->getParent();which is undoubtly better but you still have to deal with typed data internals. Or getParent isn't internals?Comment #7
berdirgetParent() is a TypedDataInterface yes, but there will be some more context specific method after #2002138: Use an adapter for supporting typed data on ContentEntities I guess.
That said, $field (which is actually a FieldItemList class) is only used to get the name of the field, wouldn't it be easier to understand if we get that from the field definition? $field_item->getFieldDefinition()->getFieldName() ?
Comment #8
stefan.r commentedComment #9
berdirThanks, I think that is easier to understand now.
Comment #10
fagoAdditional whitespace at the end of the line - fixed that.
Comment #11
fagocreated #2145103: Provide non-configurable field types for entity created, changed and timestamp fields
Comment #12
star-szrTagging for reroll.
Comment #13
fagore-rolled
Comment #16
fago13: d8_field_validation.patch queued for re-testing.
Comment #17
fagoThis needs some re-roll / test fixes
Comment #19
fgmRerolled with @covers / @coversDefaultClass annotations.
Comment #20
fgmOops, wrong window, sorry, ignore patch above.
Comment #21
fgmRerolled. For some reason, PHP fatals on my machine leaving no information behind. Let's see how it fares on the bot.
Comment #23
andypostComment #24
fgmRerolled without the typo.
Comment #26
Désiré commentedI have rerolled the patch, and fixed it. Now should not break the tests during the install.
Comment #27
Désiré commentedThe main problem with #24 was, that the user name validation wasn't worked with a field instance as it works for email, but this wasn't the only problem which broke the installation: I'm also found an other bug here: #2226811: FieldItemBase type hints DataDefinitionInterface but requires ComplexDataDefinitionInterface
Comment #29
pwolanin commentedrelated: #2226821: Combine Drupal Constraint and ConstraintValidation classes into one
Comment #30
Désiré commentedComment #31
fagoThis looks great, thanks! Imo, this is RTBC - but we'll need someone else to sign it off as I worked on it.
Comment #32
Désiré commentedIf this is really close to commit, please include mr.york (https://drupal.org/user/52785) in the commit message, he helped me a lot in debugging.
Comment #33
aspilicious commentedThis needs more spaces
Comment #34
mr.york commentedAdd spaces.
Comment #35
andypost@Désiré I've added commit message to summary
Just a few nitpicks
is this change described somewhere?
I mean that I found no docs about "context" in annotation
ConstraintValidatorInterface declares $value but $fieldItem makes more sense, but maybe it is a FieldItemList?
Comment #36
pwolanin commentedThis looks ok, but I don't see that it actually accomplishes the change suggested in the summary.
Also, seems the last patch didn't get tested? maybe need to re-post.
Comment #37
Désiré commentedI think not, how can I do it?
Yes, I'll change this.
Comment #38
andypost@Désiré the only pages I found is https://drupal.org/node/2015613
Also please check the change notices
Comment #39
mr.york commentedRenamed the $field_item values to $field_item_list in some validate function.
Comment #40
fagoOh, indeed the constraints are assigned to the item list level now. They could be assigned to the items by using
$field_definition->getItemDefinition()->setConstraints(). That does not seem so nice though, so staying at list level is fine to me.
Given that , we'd better use $list->first()->value then I guess.
Comment #41
mr.york commentedI replaced the relevant code.
Comment #43
mr.york commentedRerolle #41.
Comment #44
mr.york commentedComment #45
fagoThis is wrong now, as you cannot put it on a string property, but only on a string field (the complete list). Unfortunately, we do not have data types per field type so there is no correct type to refer here except for the generic "list" :/
Maybe, we should do
type = {"list"} and
item_type = "field_item:string". I guess this needs more work/fixing, so let's better just keep it removed/missing for now.
Comment #46
mr.york commentedRemoved the " * type = { "string" }" annotation.
Comment #47
mr.york commented46: 2110345-validation_constraints_for_fields-46.patch queued for re-testing.
Comment #48
pwolanin commentedEither the patch is incomplete or the issue summary is outdated, since a getFieldItem() method is not added
Comment #49
fagoUpdated the summary. Patch looks ready to me. Once, we've got the base class filter out NULL values for us we could even add type hinting to our $value parameter, what should help DX a lot. But we'll need #2226821: Combine Drupal Constraint and ConstraintValidation classes into one for that first.
Comment #50
yched commentedThroughout core, FieldItemList variables tend to be named $items, would be best if we stick to that ?
Comment #51
fagoI've been thinking about that also, but I wasn't sure. But as you think the same way I guess we should then.
-> Let's use
$itemsor$field_itemsas well.Comment #52
mr.york commentedRenamed the $field_item_list values to $items in relevant validate function.
Comment #53
mr.york commented52: 2110345-validation_constraints_for_fields-52.patch queued for re-testing.
Comment #54
Désiré commented52: 2110345-validation_constraints_for_fields-52.patch queued for re-testing.
Comment #55
mr.york commented52: 2110345-validation_constraints_for_fields-52.patch queued for re-testing.
Comment #56
Désiré commented52: 2110345-validation_constraints_for_fields-52.patch queued for re-testing.
Comment #58
mr.york commentedReroll.
Comment #59
fagoThanks, re-roll / patch looks good to me.
Comment #60
mr.york commented58: 2110345-validation_constraints_for_fields-58.patch queued for re-testing.
Comment #61
fagooh, I just figured that should not be added is the type is wrong. Just keep leaving it out as for the others.
Comment #62
rajendar reddy commentedUpdating patch with reroll. Please review.
Comment #63
torrance123 commentedBoth the prior state of the UserNameUnique validator and this patched version don't let me check for a unique username in the following way (following the pattern used in
User.module::user_validate_name()):In the case of the patched version I get the error
Fatal error: Call to a member function id() on a non-object in /srv/www/drupal8/core/modules/user/lib/Drupal/user/Plugin/Validation/Constraint/UserUniqueValidator.php on line 29.I'm not sure if this is because I'm using this validator incorrectly, or if this patch doesn't allow using these validators out of the context of a User entity.
Comment #64
fagoComment #65
fago62: drupal8-validation_constraints_for_fields-2110345-62.patch queued for re-testing.
Comment #66
berdir62: drupal8-validation_constraints_for_fields-2110345-62.patch queued for re-testing.
Comment #68
berdirRe-roll.
Comment #69
andypostComment #71
pfrenssenStraight reroll.
Comment #72
pfrenssenIf this is touched then also fix "a email collision" to "an email collision".
For the rest this looks RTBC to me.
Comment #73
pfrenssenFixed that little typo and cleaned up a few stray whitespace removals that crept in the patch. I didn't touch the code itself so if this comes back green it's RTBC.
Comment #74
moshe weitzman commentedAgreed - RTBC
Comment #75
alexpottThis needs to be changed to
BaseFieldDefinitionComment #77
effulgentsia commentedI asked for a retest to ensure we have test coverage (i.e., a failing test) for what is pointed out in #75.
Comment #79
effulgentsia commentedJust a reroll. Does not fix #75, so should fail if there's test coverage of that line of code.
Comment #81
effulgentsia commentedExcellent. An installation error is exactly what one would hope for when
user_validate_name()tries to invoke code that doesn't exist :)Comment #82
effulgentsia commentedNot sure why the interdiff includes the above, but if you look closely, those all cancel each other out. The user.module hunks are the only real changes.
Comment #83
fagoIndeed, that inderdiff looks strange. Anyway, changes are good - thanks, back to RTBC.
Comment #85
alexpottNeeds a reroll.
We now have no usages of
BaseFieldDefinition::setPropertyConstraints(). Shouldn't we get rid of it? Let's explore this in a followup.Comment #86
effulgentsia commentedRerolled. And filed followup for #85.
Comment #87
moshe weitzman commentedback to RTBC
Comment #88
alexpottCommitted e32a11e and pushed to 8.0.x. Thanks!
Comment #91
almaudoh commentedFiled follow-up to further simplify defining unique field constraints at #2483869: Make Unique Field constraints generic