From 291cbf9efa14460a2c77a6d6ca9bbcc2603db639 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 17 Jul 2014 16:48:59 +0200 Subject: [PATCH] [Validator] Backported #11410 to 2.3: Object initializers are called only once per object --- .../Validator/Tests/Fixtures/Entity.php | 1 + .../Validator/Tests/ValidationVisitorTest.php | 48 +++++++++++++++++++ .../Component/Validator/ValidationVisitor.php | 17 ++++--- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php index e1cb3e0490..f9e9e7ff3b 100644 --- a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php +++ b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php @@ -35,6 +35,7 @@ class Entity extends EntityParent implements EntityInterface public $reference; private $internal; public $data = 'Overridden data'; + public $initialized = false; public function __construct($internal = null) { diff --git a/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php b/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php index 2868f57a82..f2838737a2 100644 --- a/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php +++ b/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Validator\Tests; +use Symfony\Component\Validator\Constraints\Callback; +use Symfony\Component\Validator\ExecutionContextInterface; use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory; use Symfony\Component\Validator\Constraints\Valid; use Symfony\Component\Validator\Tests\Fixtures\Reference; @@ -561,4 +563,50 @@ class ValidationVisitorTest extends \PHPUnit_Framework_TestCase $this->visitor->validate($entity, 'Default', ''); } + + public function testInitializeObjectsOnFirstValidation() + { + $test = $this; + $entity = new Entity(); + $entity->initialized = false; + + // prepare initializers that set "initialized" to true + $initializer1 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface'); + $initializer2 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface'); + + $initializer1->expects($this->once()) + ->method('initialize') + ->with($entity) + ->will($this->returnCallback(function ($object) { + $object->initialized = true; + })); + + $initializer2->expects($this->once()) + ->method('initialize') + ->with($entity); + + $this->visitor = new ValidationVisitor('Root', $this->metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator(), null, array( + $initializer1, + $initializer2 + )); + + // prepare constraint which + // * checks that "initialized" is set to true + // * validates the object again + $callback = function ($object, ExecutionContextInterface $context) use ($test) { + $test->assertTrue($object->initialized); + + // validate again in same group + $context->validate($object); + + // validate again in other group + $context->validate($object, '', 'SomeGroup'); + }; + + $this->metadata->addConstraint(new Callback(array($callback))); + + $this->visitor->validate($entity, 'Default', ''); + + $this->assertTrue($entity->initialized); + } } diff --git a/src/Symfony/Component/Validator/ValidationVisitor.php b/src/Symfony/Component/Validator/ValidationVisitor.php index ddff8adc60..510c61e2eb 100644 --- a/src/Symfony/Component/Validator/ValidationVisitor.php +++ b/src/Symfony/Component/Validator/ValidationVisitor.php @@ -127,16 +127,19 @@ class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionCo return; } + // Initialize if the object wasn't initialized before + if (!isset($this->validatedObjects[$hash])) { + foreach ($this->objectInitializers as $initializer) { + if (!$initializer instanceof ObjectInitializerInterface) { + throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.'); + } + $initializer->initialize($value); + } + } + // Remember validating this object before starting and possibly // traversing the object graph $this->validatedObjects[$hash][$group] = true; - - foreach ($this->objectInitializers as $initializer) { - if (!$initializer instanceof ObjectInitializerInterface) { - throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.'); - } - $initializer->initialize($value); - } } // Validate arrays recursively by default, otherwise every driver needs