Problem/Motivation
As discovered in #2438017: Entity reference does not validate auto-created entities:
The current validation context is getting injected into constraint validator objects and in a case of a recursive validation where a validator triggers a validation chain leading to the same validator object the context of the first call will be exchanged with the one of the subsequent validation chain.
This is critical because it concerns data integrity by preventing validation errors to propagate, which might result into storing something that shouldn't have been stored.
The problem occurs at \Drupal\Core\TypedData\Validation\RecursiveContextualValidator::validateConstraints()
where we retrieve the constraint validator and intialize it the current context:
$validator = $this->constraintValidatorFactory->getInstance($constraint);
$validator->initialize($this->context);
Proposed resolution
There are 2 considerable solutions:
- Temporary switch the context on the validator by preserving the previous one.
- Stop reusing initialized constraint validator objects.
The first approach is better, but should be done in Symfony, as the base class and interface for constraint validators are provided by Symfony and they lack the ability to retrieve the context. The only possible way would be to use reflection for retrieving the previous context of the validator object.
If we want to solve the problem without reflection then we should go with the second approach.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Comment | File | Size | Author |
---|---|---|---|
#10 | 3096811-10.patch | 6.05 KB | gease |
#3 | 3096811-test-only.patch | 3.79 KB | gease |
Comments
Comment #2
hchonovComment #3
geaseUploading patch with the solution and test demonstrating the problem (recursive validation of entities, and the same field being successfully validated on the child entity eliminates error on this field for parent entity).
Comment #5
hchonovAs suggested in #2438017-65: Entity reference does not validate auto-created entities.2 we might need to get right of this class. Or if Symfony introduces a getter method on the constraint validator we might revert this change later and only temporarily exchange the context on each validator object. Please create a follow-up issue about this and reference it from here.
"Entity Reference valid reference constraint." would be a better fit.
I think that
TestRecursiveValidationConstraint
would be a better fit as an ID and class name.This looks mixed across core, but let's document the class variable like this:
$value => items
Is this check really needed?
This test file tests the
ValidReferenceConstraint
. But here we introduce a new test dealing with the recursive validation and therefore should be placed under\Drupal\KernelTests\Core\TypedData\RecursiveContextualValidatorTest
Test recursive validation.
Not needed empty line at the beginning. Here we should explain why we are creating the 2 fields and how the recursive validation is triggered.
Why is this needed?
This is unnecessary.
Document what we except here and why.
Comment #6
geaseIt's not really a valid reference constraint. It checks that the referenced entity is valid itself, but not if it can be a valid reference for this field. It's just a test. Let it be "Validates referenced entities".
I would avoid repeating word Validation. But since the constraint is actually focused on the thing that referenced entity is validated itself, let it be TestValidatedReferenceConstraint.
Ok.
Ok.
I have moved it there. And now we have in that file two tests with ridiculously differing names testRecursiveValidate and testRecursiveValidation that test almost opposite functionality: first one tests that missing referenced entity doesn't break the validation and second that referenced entity not passing the validation will also raise violation at referencing one. I'm confused.
Tried to lay out an explanation.
To avoid violation at this field :-)
But could be helpful ). I left to show that we don't set this field, but ok, removed.
I guess, expect? I put the whole explanation at the beginning of the test.
Comment #7
hchonov#5.1 still missing.
Then you can rename the new method to for example
::testRecursiveViolationPropagation()
.The label needs to be updated now as well.
This should match the constraint - "Validates referenced entities".
When documenting tests we don't need to explain what happens when something does not behave like expected. We document only what should happen. The reason is that today it fails for one reason, but tomorrow it might fail for another the the "if things don't work there will be.." might not be correct anymore. What about the following a little bit shorter text:
"We create an entity reference field with a constraint which will trigger the validation of the referenced entities. Then we add a required field and populate it only on the parent entity, so that the child entity fails the validation."
I think it makes sense to rename the entities to $parent and $child.
The new lines aren't really needed.
Instead of doing this, we could first create the $child entity and then pass it to the values of $parent when creating it.
Let's document this: "The child entity should fail the validation and the violation should propagate to the parent."
Also let's assert that the violation messages matches the expected message.
Comment #8
MeenakshiG CreditAttribution: MeenakshiG at OpenSense Labs for DrupalFit commented1. Updated label
2. Fixed comment
3. Adding suggested documentation
4. Renamed the entities to $parent and $child
5. Not fixed.
6. Not fixed.
7. Added documentation.
8. Not fixed.
Comment #9
gease#3097071: Find out how to reuse validator objects in recursive validation
Comment #10
geaseThe rest of comments addressed.
Comment #11
hchonovLooks good now. Thank you!
Comment #12
hchonovComment #13
alexpottThe static cache was added in the first fix patch of #2197029: Allow to inject dependencies into validation constraints with no comment as to why it was necessary. I think we could ask for some performance testing here BUT still think this is probably the way to go because it's obvious from Symfony's current base class that these were not designed to be re-used like this. If we want to fix the bug then this is a path forward. If we can optimise this in the future that that can happen in #3097071: Find out how to reuse validator objects in recursive validation
Comment #14
alexpottCommitted and pushed 3cc125752b to 9.0.x and 5541aeb86a to 8.9.x. Thanks!
Going to discuss with other committers about backporting to 8.8.x
Comment #17
Wim LeersWoah! 😨
Does this have security implications?
Comment #18
hchonovYes, but only in the case of nested entity references, where the constraint validator of the one level triggers the validation of the next level.
Comment #19
catch+1 to backporting this.
Comment #20
alexpott