bug #35591 [Validator] do not merge constraints within interfaces (greedyivan)

This PR was merged into the 3.4 branch.

Discussion
----------

[Validator] do not merge constraints within interfaces

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #22538
| License       | MIT
| Doc PR        |

This fix disables merge constraints within interfaces.

There is no reason to merge constraints from one interface to another because each class merges the constraints of all its interfaces. Only one check is needed is to eliminate all interfaces that comes from parent class to avoid duplication.

Commits
-------

67f336b808 do not merge constraints within interfaces
This commit is contained in:
Fabien Potencier 2020-04-12 09:28:41 +02:00
commit cd4a4bd3d1
6 changed files with 74 additions and 20 deletions

View File

@ -117,34 +117,25 @@ class LazyLoadingMetadataFactory implements MetadataFactoryInterface
private function mergeConstraints(ClassMetadata $metadata)
{
if ($metadata->getReflectionClass()->isInterface()) {
return;
}
// Include constraints from the parent class
if ($parent = $metadata->getReflectionClass()->getParentClass()) {
$metadata->mergeConstraints($this->getMetadataFor($parent->name));
}
$interfaces = $metadata->getReflectionClass()->getInterfaces();
$interfaces = array_filter($interfaces, function ($interface) use ($parent, $interfaces) {
$interfaceName = $interface->getName();
if ($parent && $parent->implementsInterface($interfaceName)) {
return false;
}
foreach ($interfaces as $i) {
if ($i !== $interface && $i->implementsInterface($interfaceName)) {
return false;
}
}
return true;
});
// Include constraints from all directly implemented interfaces
foreach ($interfaces as $interface) {
foreach ($metadata->getReflectionClass()->getInterfaces() as $interface) {
if ('Symfony\Component\Validator\GroupSequenceProviderInterface' === $interface->name) {
continue;
}
if ($parent && \in_array($interface->getName(), $parent->getInterfaceNames(), true)) {
continue;
}
$metadata->mergeConstraints($this->getMetadataFor($interface->name));
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Symfony\Component\Validator\Tests\Fixtures;
abstract class AbstractPropertyGetter implements PropertyGetterInterface
{
private $property;
public function getProperty()
{
return $this->property;
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\Validator\Tests\Fixtures;
interface ChildGetterInterface extends PropertyGetterInterface
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace Symfony\Component\Validator\Tests\Fixtures;
/**
* This class has two paths to PropertyGetterInterface:
* PropertyGetterInterface <- AbstractPropertyGetter <- PropertyGetter
* PropertyGetterInterface <- ChildGetterInterface <- PropertyGetter
*/
class PropertyGetter extends AbstractPropertyGetter implements ChildGetterInterface
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace Symfony\Component\Validator\Tests\Fixtures;
interface PropertyGetterInterface
{
public function getProperty();
}

View File

@ -14,11 +14,14 @@ namespace Symfony\Component\Validator\Tests\Mapping\Factory;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Mapping\Cache\Psr6Cache;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\PropertyGetter;
use Symfony\Component\Validator\Tests\Fixtures\PropertyGetterInterface;
class LazyLoadingMetadataFactoryTest extends TestCase
{
@ -70,7 +73,6 @@ class LazyLoadingMetadataFactoryTest extends TestCase
new ConstraintA(['groups' => [
'Default',
'EntityParentInterface',
'EntityInterfaceB',
'Entity',
]]),
];
@ -186,6 +188,15 @@ class LazyLoadingMetadataFactoryTest extends TestCase
$this->assertContains('EntityStaticCar', $groups);
$this->assertContains('EntityStaticVehicle', $groups);
}
public function testMultipathInterfaceConstraint()
{
$factory = new LazyLoadingMetadataFactory(new PropertyGetterInterfaceConstraintLoader());
$metadata = $factory->getMetadataFor(PropertyGetter::class);
$constraints = $metadata->getPropertyMetadata('property');
$this->assertCount(1, $constraints);
}
}
class TestLoader implements LoaderInterface
@ -195,3 +206,15 @@ class TestLoader implements LoaderInterface
$metadata->addConstraint(new ConstraintA());
}
}
class PropertyGetterInterfaceConstraintLoader implements LoaderInterface
{
public function loadClassMetadata(ClassMetadata $metadata)
{
if (PropertyGetterInterface::class === $metadata->getClassName()) {
$metadata->addGetterConstraint('property', new NotBlank());
}
return true;
}
}