Skip to content

Commit

Permalink
[Validator] Fixed object initializers in 2.5 version of the Validator
Browse files Browse the repository at this point in the history
  • Loading branch information
webmozart committed Jul 21, 2014
1 parent a9af6be commit ce04073
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 36 deletions.
40 changes: 40 additions & 0 deletions src/Symfony/Component/Validator/Context/ExecutionContext.php
Expand Up @@ -18,6 +18,7 @@
use Symfony\Component\Validator\Exception\BadMethodCallException;
use Symfony\Component\Validator\Mapping\MetadataInterface;
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
use Symfony\Component\Validator\ObjectInitializerInterface;
use Symfony\Component\Validator\Util\PropertyPath;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
Expand Down Expand Up @@ -113,6 +114,13 @@ class ExecutionContext implements ExecutionContextInterface
*/
private $validatedConstraints = array();

/**
* Stores which objects have been initialized.
*
* @var array
*/
private $initializedObjects;

/**
* Creates a new execution context.
*
Expand Down Expand Up @@ -360,4 +368,36 @@ public function isConstraintValidated($cacheKey, $constraintHash)
{
return isset($this->validatedConstraints[$cacheKey.':'.$constraintHash]);
}

/**
* Marks that an object was initialized.
*
* @param string $cacheKey The hash of the object
*
* @internal Used by the validator engine. Should not be called by user
* code.
*
* @see ObjectInitializerInterface
*/
public function markObjectAsInitialized($cacheKey)
{
$this->initializedObjects[$cacheKey] = true;
}

/**
* Returns whether an object was initialized.
*
* @param string $cacheKey The hash of the object
*
* @return bool Whether the object was already initialized
*
* @internal Used by the validator engine. Should not be called by user
* code.
*
* @see ObjectInitializerInterface
*/
public function isObjectInitialized($cacheKey)
{
return isset($this->initializedObjects[$cacheKey]);
}
}
1 change: 1 addition & 0 deletions src/Symfony/Component/Validator/Tests/Fixtures/Entity.php
Expand Up @@ -38,6 +38,7 @@ class Entity extends EntityParent implements EntityInterface
public $reference2;
private $internal;
public $data = 'Overridden data';
public $initialized = false;

public function __construct($internal = null)
{
Expand Down
Expand Up @@ -42,7 +42,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest
*
* @return ValidatorInterface
*/
abstract protected function createValidator(MetadataFactoryInterface $metadataFactory);
abstract protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array());

protected function setUp()
{
Expand Down Expand Up @@ -678,4 +678,52 @@ public function testAccessCurrentObject()

$this->assertTrue($called);
}

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->validator = $this->createValidator($this->metadataFactory, 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
$validator = $context->getValidator()->inContext($context);

$validator->validate($object);

// validate again in other group
$validator->validate($object, null, 'SomeGroup');
};

$this->metadata->addConstraint(new Callback($callback));

$this->validate($entity);

$this->assertTrue($entity->initialized);
}
}
Expand Up @@ -38,7 +38,7 @@ abstract class AbstractLegacyApiTest extends AbstractValidatorTest
*
* @return LegacyValidatorInterface
*/
abstract protected function createValidator(MetadataFactoryInterface $metadataFactory);
abstract protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array());

protected function setUp()
{
Expand Down Expand Up @@ -238,6 +238,52 @@ public function testAddCustomizedViolation()
$this->assertSame('Code', $violations[0]->getCode());
}

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->validator = $this->createValidator($this->metadataFactory, 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($callback));

$this->validate($entity);

$this->assertTrue($entity->initialized);
}

public function testGetMetadataFactory()
{
$this->assertSame($this->metadataFactory, $this->validator->getMetadataFactory());
Expand Down
Expand Up @@ -28,10 +28,10 @@ protected function setUp()
parent::setUp();
}

protected function createValidator(MetadataFactoryInterface $metadataFactory)
protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array())
{
$contextFactory = new LegacyExecutionContextFactory($metadataFactory, new DefaultTranslator());

return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory());
return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory(), $objectInitializers);
}
}
Expand Up @@ -28,10 +28,10 @@ protected function setUp()
parent::setUp();
}

protected function createValidator(MetadataFactoryInterface $metadataFactory)
protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array())
{
$contextFactory = new LegacyExecutionContextFactory($metadataFactory, new DefaultTranslator());

return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory());
return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory(), $objectInitializers);
}
}
Expand Up @@ -19,10 +19,10 @@

class RecursiveValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
{
protected function createValidator(MetadataFactoryInterface $metadataFactory)
protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array())
{
$contextFactory = new ExecutionContextFactory(new DefaultTranslator());

return new RecursiveValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory());
return new RecursiveValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory(), $objectInitializers);
}
}
4 changes: 2 additions & 2 deletions src/Symfony/Component/Validator/Tests/ValidatorTest.php
Expand Up @@ -21,9 +21,9 @@

class ValidatorTest extends AbstractLegacyApiTest
{
protected function createValidator(MetadataFactoryInterface $metadataFactory)
protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array())
{
return new LegacyValidator($metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator());
return new LegacyValidator($metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator(), 'validators', $objectInitializers);
}

/**
Expand Down
17 changes: 10 additions & 7 deletions src/Symfony/Component/Validator/ValidationVisitor.php
Expand Up @@ -129,16 +129,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
Expand Up @@ -27,6 +27,7 @@
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
use Symfony\Component\Validator\Mapping\TraversalStrategy;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\ObjectInitializerInterface;
use Symfony\Component\Validator\Util\PropertyPath;

/**
Expand All @@ -52,23 +53,30 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
*/
private $validatorFactory;

/**
* @var ObjectInitializerInterface[]
*/
private $objectInitializers;

/**
* Creates a validator for the given context.
*
* @param ExecutionContextInterface $context The execution context
* @param MetadataFactoryInterface $metadataFactory The factory for
* fetching the metadata
* of validated objects
* @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
* constraint validators
* @param ExecutionContextInterface $context The execution context
* @param MetadataFactoryInterface $metadataFactory The factory for
* fetching the metadata
* of validated objects
* @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
* constraint validators
* @param ObjectInitializerInterface[] $objectInitializers The object initializers
*/
public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory)
public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = array())
{
$this->context = $context;
$this->defaultPropertyPath = $context->getPropertyPath();
$this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP);
$this->metadataFactory = $metadataFactory;
$this->validatorFactory = $validatorFactory;
$this->objectInitializers = $objectInitializers;
}

/**
Expand Down Expand Up @@ -432,6 +440,14 @@ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $m
{
$context->setNode($object, $object, $metadata, $propertyPath);

if (!$context->isObjectInitialized($cacheKey)) {
foreach ($this->objectInitializers as $initializer) {
$initializer->initialize($object);
}

$context->markObjectAsInitialized($cacheKey);
}

foreach ($groups as $key => $group) {
// If the "Default" group is replaced by a group sequence, remember
// to cascade the "Default" group when traversing the group
Expand Down
30 changes: 20 additions & 10 deletions src/Symfony/Component/Validator/Validator/RecursiveValidator.php
Expand Up @@ -15,6 +15,7 @@
use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\ObjectInitializerInterface;

/**
* Recursive implementation of {@link ValidatorInterface}.
Expand All @@ -39,22 +40,29 @@ class RecursiveValidator implements ValidatorInterface
*/
protected $validatorFactory;

/**
* @var ObjectInitializerInterface[]
*/
protected $objectInitializers;

/**
* Creates a new validator.
*
* @param ExecutionContextFactoryInterface $contextFactory The factory for
* creating new contexts
* @param MetadataFactoryInterface $metadataFactory The factory for
* fetching the metadata
* of validated objects
* @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
* constraint validators
* @param ExecutionContextFactoryInterface $contextFactory The factory for
* creating new contexts
* @param MetadataFactoryInterface $metadataFactory The factory for
* fetching the metadata
* of validated objects
* @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
* constraint validators
* @param ObjectInitializerInterface[] $objectInitializers The object initializers
*/
public function __construct(ExecutionContextFactoryInterface $contextFactory, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory)
public function __construct(ExecutionContextFactoryInterface $contextFactory, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = array())
{
$this->contextFactory = $contextFactory;
$this->metadataFactory = $metadataFactory;
$this->validatorFactory = $validatorFactory;
$this->objectInitializers = $objectInitializers;
}

/**
Expand All @@ -65,7 +73,8 @@ public function startContext($root = null)
return new RecursiveContextualValidator(
$this->contextFactory->createContext($this, $root),
$this->metadataFactory,
$this->validatorFactory
$this->validatorFactory,
$this->objectInitializers
);
}

Expand All @@ -77,7 +86,8 @@ public function inContext(ExecutionContextInterface $context)
return new RecursiveContextualValidator(
$context,
$this->metadataFactory,
$this->validatorFactory
$this->validatorFactory,
$this->objectInitializers
);
}

Expand Down

0 comments on commit ce04073

Please sign in to comment.