Postponed on #2343035: Upgrade validator integration for Symfony versions 2.5+, see #46.
Motivation
For #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay we need to solve conditional field validation, i.e. validation value of field A in dependence of the value field B. #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay requires that we have knowledge about all involved fields A and B.
As #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay is critical, bumping this to critical as well.
Proposed resolution
Add a new base class for such constraints and call it CompositeConstraintBase
.
Ensure covered fields are specified for that constraints and test that information is available from violations.
Convert our single use-case in core CommentNameConstraintValidator
to the new base class.
Remaining tasks
Get #2343035: Upgrade validator integration for Symfony versions 2.5+ done.
Implement it.
User interface changes
-
API additions
- Constrains/violations which act on multiple fields can now be defined using
Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase
- There constrains can specify via the
coversFields()
method to which fields they apply to - Those constraints have to be added on the entity level
- In case the a validator uses
addViolation()
with either $invalidValue, $plural or $code,
for compatibility with symfony 3.0 and above violations should now be added using the following code:$context->buildViolation('Please enter a number between %min% and %max%.') ->setParameter('%min%', 3) ->setParameter('%max%', 10) ->addViolation();
Original report by fago:
Quoting from #2015687-46: Convert field type to FieldType plugin for taxonomy module which describes a needed feature for entity validation:
2). A conditional field. For example, a field contains two elements, when user chooses 'pre-defined' for first element, we need to validate the other element for options list. When user chooses 'Custom', we don't need to validate the pre-defined option list.
Right now, you can do that by
a) writing your own constraint plugin or
b) use the Callback constraint + a custom validation callback instead
b) dynamically providing your own constraints from within getConstraints().
c) Symfony suggests using validation groups: http://symfony.com/doc/current/book/validation.html#group-sequence-provi...
Let's figure out what the pos/cons are on this topic and document what's the best approach.
Related symfony PR (that got reverted) - "dynamic constraints".
Comment | File | Size | Author |
---|---|---|---|
#82 | interdiff.txt | 1.39 KB | dawehner |
#82 | 2105797-82.patch | 13.59 KB | dawehner |
#77 | interdiff.txt | 2.28 KB | dawehner |
#77 | 2105797-77.patch | 14.08 KB | dawehner |
#75 | interdiff.txt | 3.19 KB | dawehner |
Comments
Comment #1
fagotagging
Comment #2
fagoAs discussed with webmozart, dynamically defined constraints do not really make sense, and do not allow us to benefit from having upfront defined constraints any more, e.g. adding a way to translate constraints to JS based constraint validation.
Comment #3
larowlanThis blocks criticals
Comment #4
webchickHm, can you clarify? #2278523: Avoid dynamically defining constraints (in related issues) is only a major. And the only other issue it references is #2023465: Allow validation constraints to add in message replacements which is also a major.
Comment #5
catch#2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay I think.
Comment #6
larowlan#2403485: Complete conversion of comment form validation to entity validation it blocks
Comment #7
larowlanoptimistically taking this on, if you don't see anything in a week or so, send a search party, I'll be trapped in typed data hell
Comment #8
larowlanFrom what I can see - this might already be done - the ConstraintValidator classes are passes a FieldItemList as arguments, which means the entity ($items->getEntity()) is available, meaning although a validator is defined for a single field, it has access to all field values.
I don't think this blocks #2403485: Complete conversion of comment form validation to entity validation anymore, so going to demote this and unpostpone that. See you over there.
Comment #9
dawehnerIt turns out that you want to validate more than the stuff defined by your entity type, but you want to be able to apply business logic on your validation as well.
For that usecase we need a way for this, see #2405503: How to validate a REST submitted node?
Comment #10
larowlanWhich would make #2346329: hook_entity_base_field_info_alter() and hook_entity_bundle_field_info_alter() are documented to get a parameter that doesn't implement an interface with setter methods critical again?
Comment #11
dawehner@larowlan
Note: I don't think that its the best idea to pin that kind of validation onto base fields directly. We are talking about things you normally just do for some bundles in a custom module so having an api which scales horizontal would be nice.
Comment #12
fagoYes, it already is easily possible to do it somehow, but we need to do it in a secure way, such that entity forms do not wrongly ignore / not-run relevant validation logic. See #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay for more about the problems around what to to validate and mapping/ignore violations. Howsoever, yeah one can definitely already start converting the validtion logic into constraints and put it on the field where the violation would show up. Then, the remaining bit would be "only" making it safe.
You can add the constraints via hook_entity_base_field_info_alter().
Comment #13
fagoFiguring out this is required for solving #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay. Updated the issue summary accordingly and added a proposed resolution.
While discussing this with webmozart, webmozart suggested using the term "CompositeConstraint". I like it as ConditionalConstraint isn't descriptive at all, every constraint is a conditional. Composite seems to fit as it will check multiple things.
Comment #14
fagoComment #15
larowlanwhy not
Comment #16
larowlanSo I think we can use something in a similar fashion to http://symfony.com/doc/current/reference/constraints/Collection.html here, but we need #2429037: Allow adding entity level constraints first to be able to add the composite constraint to the comment entity and replace CommentNameConstraintValidator with something similar - so I think that then makes this postponed as well - but will work on a patch that also includes #2429037: Allow adding entity level constraints as it stands.
Comment #17
larowlanmy brain can't process this at the moment
Comment #18
fagoI don't think Collection buys us anything here - we already have the ComplexData constraint which basically is our variant of the symfony Collection.
Comment #19
dawehnerI'm curious how horrible wrong this is.
Comment #20
larowlanand now my brain can process it @dawehner++ code > text
Comment #21
dawehnerLet's see whether the testbot likes us
Comment #23
dawehnerThis time with more manual testing.
Comment #24
dawehnerAnd some documentation / test coverage.
Comment #25
larowlanI have two questions about how you would work with a FieldItem instead of the scalar values in case you need access to more than just the main property. And what happens if you want to validate the second or third item in the field item list (or access the whole FieldItemList) ?
I personally think that passing the field name in fields should get you the field item list. The validator should deal with whether it wants the first/second/each value or the required properties - also if you know you want a specific property, we could also support [fields => [field_foo.0.value]] style format.
Eg in summary
['fields' = ['field_foo']]; // Gets you the FieldItemList.
['fields' = ['field_foo.0']]; // Gets you the FieldItem at delta 0.
['fields' = ['field_foo.0.title']]; // Gets you the scalar value of the title property of delta 0.
We should add the third parameter here, but as mentioned above we should allow up to three (name, delta, property)
I think wiring this to first() is wrong
can we get a docblock and a @covers here?
Comment #26
fagoCan we just make an abstract method, such that one is required to implement it?
Not sure there is value in providing a helper method like that, why would i use that instead of directly accessing the fields from $entity ?
Comment #27
fagowell, the value I see is that it helps to ensure people are only using the fields they are supposed to. But for that, having an array of field item objects would suffice as well, e.g.:
But still, you can use the regular API and there we can save the possibly unnecessary preprocessing work?
Comment #28
dawehnerIf you don't specify the fields you want from outside, what exactly is the major gain of the base class? Can't you pull all the values you want
already from HEAD? I try to figure out where exactly the value would be, if you still have to do it manually?
Comment #29
larowlanYes, this issue made no sense to me, see #15, #16, #17 - until I saw it in code from @dawehner. If you remove the getFields, then I'm back at the 'makes no sense to me' station
Comment #30
larowlanComment #31
larowlanI was thinking about how this applies to the blocked issue on entity-form displays and how the code might look if this were in. In my opinion if we had an interface for composite constraints the code would be nicer because we'd get
if ($constraint instanceof CompositeConstraintInterface)
instead of usingis_subclass_of()
Also, if what @dawehner said via irc in #30 is correct, do we need to be using 'name' in 'fields' in the conversion of CommentNameAuthorConstraint (sp?) if name is the field it is attached to already? Should we just be tracking fields that the constraint depends on in addition to the field it is attached to?
Comment #33
fagoquite so - it's such that #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay can do its job for composite constraints and add it in as soon as such one of the involved fields is shown on the form. If that was not clear from the issue summary, please help clarifying it!
I don't see the relationship, instanceof can be used with classes also? As constraints are value objects and do not have interfaces in symfony either, I'd rather not add one?
Problem still is how to handle access to the entity best. People should not use other field values, but as the comment name validator shows there are cases where you need access to the entity still. That's fine as long as it's not used to access other un-covered fields. As it's always possible somehow to do $items->getEntity()->other_field I guess we can only solve this by documentation.
Comment #34
dawehnerI honestly still don't see how the entity display validation should be aware, of which fields are covered. Does that mean its code has to special case the new base constraint?
Comment #35
larowlanSo thinking ahead to #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay we're going to collect entity-level constraints in the form display, and filter out those that don't apply, then apply the remaining.
As I understand it, the validate method on the entity doesn't accept an array of constraints, how are we going to pass that context to the validator?
I can't fault the code as it stands, but I'm not sure it gets us towards the end-solution just yet.
Comment #36
fagoyes, that's the only way it's going to work. There is nothing existing we can build upon, we have to add that. Symfony validator does not support multiple property paths covered for a constraint.
We can filter the violations once they are created, based upon their constraint, property path (if not composite) and field access.
Comment #37
larowlanPerfect, that makes total sense now, thank you
Comment #38
dawehnerEntityFormDisplay
, but it seems to be impossible to get the constraint from a given violation.\Symfony\Component\Validator\ExecutionContext::addViolation
does not add the constraint,\Symfony\Component\Validator\Context\ExecutionContext::addViolation
doesn't do it, so we maybe have to wait for #2343035: Upgrade validator integration for Symfony versions 2.5+Comment #39
fagohm, I was thinking to add the violation filtering logic in #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay and just make sure the implementation of composite contraints done here provides the metadata necessary for it. #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay already prepares a patch for it, so could we work on it there? I'd be happy about reviews about the approach taken there!
ouch, good finding :-/
Comment #40
fagoSo what it already adds is the violation code, so instead we could use the violation code for now. That would work, but requires the validator developer to take care of that, what is quite bad for DX. So looks like #2343035: Upgrade validator integration for Symfony versions 2.5+ is indeed a dependency for filtering those violations :(
As we have to continue to pass on $entity to the validator, I agree - there is no benefit left.
Comment #41
dawehner@fago
So should we better postpone onto #2343035: Upgrade validator integration for Symfony versions 2.5+ and then finish this issue?
Comment #42
fagoNot sure, if possible I'd love to avoid that to not unnecessarily stall things - but if we do not find another way that seems to be the logical consequence.
Comment #43
effulgentsia CreditAttribution: effulgentsia commentedDiscussed with the branch maintainers and they agreed this is critical, given that core has such a constraint, making this a hard blocker for #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay. Tagging, retitling, and see also #2395831-54: Entity forms skip validation of fields that are not in the EntityFormDisplay.
Comment #44
effulgentsia CreditAttribution: effulgentsia commentedAlso adding the blocker tag, since another issue is postponed on this.
Comment #45
effulgentsia CreditAttribution: effulgentsia commentedAlso tagging for security and WSCCI, as it is required for secure web services.
Comment #46
fagoActually, making sure composite constraints will be executed in all necessary situations won't work before #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay gets done as well (which depends on this issue), thus re-titling to the issue scope of just discovering composite constraints and the fields covered.
However, as figured out in comments #38-#42 we need to get #2343035: Upgrade validator integration for Symfony versions 2.5+ done before in order to be able to know the constraint which caused a violation. The only way I see to not postpone this now would be adding an interim solution based on certain a violation code which the validator would have to specify when adding a violation. But that would be an burden for validator implementers + an API change when that burden gets removed later on.
Thus, in order to avoid those changes I think it's best to postpone this issue on #2343035: Upgrade validator integration for Symfony versions 2.5+.
Comment #47
fagoComment #48
effulgentsia CreditAttribution: effulgentsia commentedPer #46.
Comment #49
effulgentsia CreditAttribution: effulgentsia commentedComment #50
Crell CreditAttribution: Crell at Palantir.net commentedThere's a massive amount of context here that I don't have... what exactly is needed from the WSCCI side?
Comment #51
effulgentsia CreditAttribution: effulgentsia commentedI don't think there's anything needed here from the WSCCI team, but I tagged it WSCCI in #45 due to there being potential security problems (data validations not executed) in non-form REST submissions until this is fixed.
Comment #52
jibranComment #53
jibranComment #54
dawehnerOh sure, let's do that there . This is for now just a reroll.
Comment #56
dawehnerThis test doesn't make sense anymore now that we don't longer have the base class ...
Given that fago also wanted to introduce the actual form integration in #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay it doesn't really make sense to test something related to this here.
I think its fine to keep EntityTestCompositeConstraint as it can be used in the future.
Beside from that there is nothing to do here, honestly.
Comment #57
larowlanYep, we just needed the 2.5 API so we could get the constraint from the violation. Which we have.
In my books, this is RTBC except for:
This would have referenced #2429037: Allow adding entity level constraints but that is since fixed. So the See ... @todo can instead be @see \Drupal\Core\Entity\EntityType::addConstraint or a reference to the annotation.
Comment #58
dawehnerThere we go.
Comment #59
larowlanunless bot disagrees, but possibly even if bot disagrees (bots have been misbehaving today)
Comment #60
amateescu CreditAttribution: amateescu for Drupal Association commentedThe patch looks good to me as well, I just found two small nitpicks:
Is this really necessary? Wouldn't it prevent someone from overriding the User entity class and use "machine names" for the id? Sure, they would need to keep '0' as the machine name for the anonymous user but other than that it should be possible :)
If you're going to address the point above (i.e. if you roll a new patch), how about also changing the message here to "Composite constraint violation is generated correctly"?
Comment #61
larowlan(int) $entity->uid->target_id
This is consistent with EntityOwnerInterface
Comment #62
yched CreditAttribution: yched commentedThe "name, uid, entity" part looks specific to the CommentNameConstraint concrete class ?
Also, is that really up to date ? Implementations in the patch seem to be doing
$entity = $value->getEntity()
?Comment #63
fagoYes, yched is right - the constraints are not defined at the right level (any more?).
Exactly.
So this constraint needs to be moved also.
This test coverage is bogus as the constraint is supposed to be added on entity level.
Comment #64
fagoI'll take a look.
Comment #67
fagoAdapted the constraints accordingly so they operate on the right level.
There are still some things left:
- We need to add test coverage for the test constraint and test we can detect composite constraint violations
- It would be good to ensure composite constraints are put on entity level.
- Generally, I wonder whether we should stop using ->context->addViolation() in favour of the new 2.5 buildViolation() API. Should be a separate issue though.
I'll continue later on the first two points.
Comment #68
dawehnerI don't think we have to explicit document that its compatible with 2.5 and above ... the interface already has that kind of information.
Urgs, so we now have to use that new API which is a little bit more verbose?
Comment #69
fagoI now, I just wanted to point out that's the reason for redefining the variable here. It's already defined in the parent - but not with the 2.5 interface. Maybe, we should just re-define a Drupal base validator class which has that? Seems useful for everyone that needs to use buildViolation().
Yes, for every call that wants to pass anything else than message + replacements. However, IDEs mark every call to addViolation() as deprecated as they do not know it's only about the added parameters.
Comment #70
fagoSo attached patch adds test coverage that ensures we can determine which constraints are composite. That way we know the covered fields and #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay is able to filter constraints accordingly - or not.
Not sure how we'd do that best. It doesn't fit into the validation integration as that operates on Typed Data level, so maybe best it would just go into a composite constraint validator base class? Still, no one would enforce you are using that but I guess it should be enough to cover typical accidential errors?
Comment #71
jibranThis is an api addition so API change section needs an update in IS and we need a change notice as well.
This doesn't make sense. There is no class name
CompositeConstraintValidatorBase
neither in this patch nor in core.I know we need a change notice overall but we need to mention this in it as well.
Comment #74
dawehnerAdded an IS/CR
Comment #75
dawehnerGood catch! This is now entirely not needed anymore.
I hope the fix for the CommentAnoymousTest is now the proper way to do, before we have the custom violation list introduced in #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay
Comment #77
dawehnerThere we go, this time with the right bug fix.
Comment #78
jibranThanks for the fixes.
Why not ditch this and use $violation->getPropertyPath() as a first param of the setErrorByName Method?
Comment #79
dawehnerWell, we just want to take into account violations against the name field here, other violations, like for fields itself should be ignored here.
They are handled in the entity display form component.
Yes this means that at the moment, we run most of the validations twice for comments.
Comment #80
fagoChecking my on test, I think we should add test coverage for the violation being added on the right sub-path also.
Great this works now, but this change is now useless.
ad buildViolation():
The change is only required if you pass any additional arguments beside message + message params. But that's easy missed when addViolation() is used else - thus I'm wondering whether it would make sense to promote using buildViolation() always, or at least always for composite contraints? It's a little bit more verbose, but less error-prone. In particular composite constraints will have to add sub-paths.
Comment #81
fagoRight. We could prevent it by validating only entity-level constraints manually - but given that's only there until #2395831: Entity forms skip validation of fields that are not in the EntityFormDisplay is fixed I do not care.
Comment #82
dawehnerGood points, addressed them quickly.
Comment #83
jibranI think only in composite constraints is fine.
Nice.
I think everything is done here.
Comment #84
fagoThanks - I agree this is ready to go.
I took another look at the CR draft and improved it a little. The buildViolation() change mentioned there has actually been introduced by https://www.drupal.org/node/2343035 - we missed it there as there was no place we had to convert in core so far. Thus, I removed it from this CR draft and updated https://www.drupal.org/node/2480711 instead to mention this change.
Comment #85
alexpottThis issue addresses a critical bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 69ce355 and pushed to 8.0.x. Thanks!
Fixed on commit.