Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Validator] Don't add multiple MemberMetadata for the same property and the same class when merging constraints. #22571

Closed
wants to merge 4 commits into from

Conversation

fancyweb
Copy link
Contributor

@fancyweb fancyweb commented Apr 28, 2017

Q A
Branch? 2.7
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #22538
License MIT
Doc PR -

Hello, before reading my comments, you should look at the fixed ticket for more context.

So, given the following classes (taken from the example) :

interface PriceInterface {}
interface VariantPriceInterface extends PriceInterface {}
abstract class AbstractPrice implements PriceInterface {}
class VariantSamplePrice extends AbstractPrice implements VariantPriceInterface {}

and validation file :

PriceInterface:
    getters:
        value:
            - NotBlank: ~

To begin, I tried to improve the work done by @lemoinem to fix the previous issue of double validation I reported (cf #19943). I tried to make it work with the new case I have found but I couldn't. IMO, it is impossible without changing some public methods signatures. I will describe the problem with the example classes.

The problem is that both class AbstractPrice and interface VariantPriceInterface implements the PriceInterface interface but when you get their metadata, you get them independently. I mean there is no "context" that indicates that in reality, we want the metadata for the VariantSamplePrice model. In other words, both classes don't know about each other, they cannot know that they both implement the same interface, and have no way of knowing if the constraints for the PriceInterface have already been added or no.

So I had a different approach, and thought that I could "clean" the duplicated constraints after they were all added. There are several ways to do this, I chose to make the check as soon as possible, when the constraints are merged, so the metadata are in an "invalid" state the shortest time. The check is simple : don't merge MemberMetadata constraints if there are already defined for the same property and the same class.

To illustrate with my example : at a given time, the VariantSamplePrice metadata will already have a GetterMetadata for the property value of the class PriceInterface either coming from the AbstractPrice or the VariantPriceInterface metadata. Then, the PriceInterface metadata will be merged a second time in the VariantSamplePrice metadata (coming from the other class metadata). But with the check, the constraints won't be merged another time. Because a MemberMetadata for the same property and of the same class exists so it means that the exact same constraints have already been added.

My main concern is the choice of which MemberMetadata should be kept as there are not exactly the same : depending of of its class, the groups in the constraints are not the same. I honestly don't know if this is a problem or not.

Also, I originally deleted @lemoinem work because it was not really needed anymore and it covers less cases. But actually, as long as there are only interfaces concerned, it works better because the metadata are never "invalid". But I don't know if it's logical to keep both checks in different files, I need advice on this.

@nicolas-grekas nicolas-grekas added this to the 2.7 milestone Apr 28, 2017

$metadata->mergeConstraints($otherMetadata);

$this->assertCount(1, $metadata->getPropertyMetadata('data'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the two constraints are different? So this should be 2 here imho.

Copy link
Contributor

@ro0NL ro0NL May 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's just a test :) the behavior is actually correct imo. Skip constraints from the same class/property (i.e. the ones we already visited). in real life these are the same ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, the constraint doesn't matter. What matters in this test is that it's the same metadata class (self::INTERFACE_A_CLASS) and the same getter "property" (data). However, I agree that the constraint should maybe be the same so it's easier to understand.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i actually liked you used different constraints :P it shows we're testing behavior

Copy link
Contributor

@dmaicher dmaicher May 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I would agree that they are always "the same" in "real-life" 😉 Seems like a BC break to me.

Imho we would have to check if the constraints are actually 100% the same, so same class & same options. Only then we have real duplicates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you're right.. we cannot foresee the possible things people are doing. I dont like the idea of a deep comparison nightmare; see #15950 which we need to consider as well.

I think we actually need to prevent the wrong call to mergeConstraint based on the above inheritance.

@dmaicher
Copy link
Contributor

dmaicher commented May 3, 2017

One different approach that I'm using in one of my apps is that I simply only render "unique" error messages as this simply makes the most sense to the end-user.

Can we maybe go into that direction? So even if constraints are duplicated we move the de- duplication to the validation phase?

Just an idea 😋

@ro0NL
Copy link
Contributor

ro0NL commented May 3, 2017

clever.. :) but still imo. this needs a fix low-level. Like done in #20078; filtering out duplicated interfaces. That needs to cover above usecase.

@ro0NL
Copy link
Contributor

ro0NL commented May 5, 2017

@fancyweb i played with it yesterday, and you're absolutely right. The approach is hard... (i re-read you issue once more) and you encountered everything already :P

However, i think i got something (forgot to push, but i can show you later). My idea was to track visited classes, and simply skip ones we already visited. It seems to work, in terms of i get the same no. of constraints. However some constraints have different groups (BC break), which probably means we need to add implicit groups for the classes we skipped (not sure, here's where i stopped :))

The failing test would be

diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php b/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php
index 4674f8b35a..8aaf21b719 100644
--- a/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/EntityParent.php
@@ -13,7 +13,7 @@ namespace Symfony\Component\Validator\Tests\Fixtures;
 
 use Symfony\Component\Validator\Constraints\NotNull;
 
-class EntityParent implements EntityInterfaceA
+class EntityParent implements EntityInterfaceA, EntityParentInterface
 {
     protected $firstName;
     private $internal;

@nicolas-grekas
Copy link
Member

ping @fancyweb

@fancyweb
Copy link
Contributor Author

We can close this as my solution is more a hack than a fix and it breaks BC. I'm working on a new solution atm and will open a new PR...

@fancyweb fancyweb closed this Jan 22, 2018
@fancyweb fancyweb deleted the double-validation-interfaces branch August 9, 2019 07:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants