diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php index c9e30b458780f..c372f341eb369 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidatorTest.php @@ -12,9 +12,12 @@ namespace Symfony\Component\Validator\Tests\Validator; use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Constraints\IsFalse; +use Symfony\Component\Validator\Constraints\IsNull; use Symfony\Component\Validator\Constraints\IsTrue; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; @@ -22,6 +25,7 @@ use Symfony\Component\Validator\Constraints\Optional; use Symfony\Component\Validator\Constraints\Required; use Symfony\Component\Validator\Constraints\Valid; +use Symfony\Component\Validator\ConstraintValidator; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\Context\ExecutionContextFactory; use Symfony\Component\Validator\Mapping\ClassMetadata; @@ -200,4 +204,47 @@ public function testOptionalConstraintIsIgnored() $this->assertCount(0, $violations); } + + public function testValidatedConstraintsHashesDontCollide() + { + $metadata = new ClassMetadata(Entity::class); + $metadata->addPropertyConstraint('initialized', new NotNull(['groups' => 'should_pass'])); + $metadata->addPropertyConstraint('initialized', new IsNull(['groups' => 'should_fail'])); + + $this->metadataFactory->addMetadata($metadata); + + $entity = new Entity(); + $entity->data = new \stdClass(); + + $this->assertCount(2, $this->validator->validate($entity, new TestConstraintHashesDontCollide())); + } +} + +final class TestConstraintHashesDontCollide extends Constraint +{ +} + +final class TestConstraintHashesDontCollideValidator extends ConstraintValidator +{ + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) + { + if (!$value instanceof Entity) { + throw new \LogicException(); + } + + $this->context->getValidator() + ->inContext($this->context) + ->atPath('data') + ->validate($value, new NotNull()) + ->validate($value, new NotNull()) + ->validate($value, new IsFalse()); + + $this->context->getValidator() + ->inContext($this->context) + ->validate($value, null, new GroupSequence(['should_pass'])) + ->validate($value, null, new GroupSequence(['should_fail'])); + } } diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php index a51e66d2906d0..ba3cbe640c3ee 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php @@ -49,6 +49,9 @@ class RecursiveContextualValidator implements ContextualValidatorInterface private $validatorFactory; private $objectInitializers; + private $validatedConstraintsReferences = []; + private $initializedObjectsReferences = []; + /** * Creates a validator for the given context. * @@ -443,6 +446,10 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m { $context->setNode($object, $object, $metadata, $propertyPath); + if (!isset($this->initializedObjectsReferences[$cacheKey])) { + $this->initializedObjectsReferences[$cacheKey] = $object; + } + if (!$context->isObjectInitialized($cacheKey)) { foreach ($this->objectInitializers as $initializer) { $initializer->initialize($object); @@ -456,9 +463,7 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m // to cascade the "Default" group when traversing the group // sequence $defaultOverridden = false; - - // Use the object hash for group sequences - $groupHash = \is_object($group) ? spl_object_hash($group) : $group; + $groupHash = serialize($group); if ($context->isGroupValidated($cacheKey, $groupHash)) { // Skip this group when validating the properties and when @@ -803,6 +808,10 @@ private function validateInGroup($value, $cacheKey, MetadataInterface $metadata, // that constraints belong to multiple validated groups if (null !== $cacheKey) { $constraintHash = spl_object_hash($constraint); + if (!isset($this->validatedConstraintsReferences[$constraintHash])) { + $this->validatedConstraintsReferences[$constraintHash] = $constraint; + } + // instanceof Valid: In case of using a Valid constraint with many groups // it makes a reference object get validated by each group if ($constraint instanceof Composite || $constraint instanceof Valid) {