Skip to content

Commit

Permalink
[Validator] Backported #11410 to 2.3: Object initializers are called …
Browse files Browse the repository at this point in the history
…only once per object
  • Loading branch information
webmozart committed Jul 18, 2014
1 parent 91e32f8 commit 291cbf9
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Component/Validator/Tests/Fixtures/Entity.php
Expand Up @@ -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)
{
Expand Down
48 changes: 48 additions & 0 deletions src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php
Expand Up @@ -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;
Expand Down Expand Up @@ -561,4 +563,50 @@ public function testValidateCascadedPropertyRequiresObjectOrArray()

$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);
}
}
17 changes: 10 additions & 7 deletions src/Symfony/Component/Validator/ValidationVisitor.php
Expand Up @@ -127,16 +127,19 @@ public function validate($value, $group, $propertyPath, $traverse = false, $deep
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
Expand Down

0 comments on commit 291cbf9

Please sign in to comment.