bug #11411 [Validator] Backported #11410 to 2.3: Object initializers are called only once per object (webmozart)

This PR was merged into the 2.3 branch.

Discussion
----------

[Validator] Backported #11410 to 2.3: Object initializers are called only once per object

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

Before, object initializers were called multiple times if an object was validated in different groups in the same validation run. The initializers, however, are not aware of the current validation group, so calling them more than once does not make sense.

Now, object initializers are called exactly once per validated object.

See #11410

Commits
-------

291cbf9 [Validator] Backported #11410 to 2.3: Object initializers are called only once per object
This commit is contained in:
Fabien Potencier 2014-07-18 11:07:06 +02:00
commit 9572918064
3 changed files with 59 additions and 7 deletions

View File

@ -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)
{

View File

@ -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);
}
}

View File

@ -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