[Validator] Fixed object initializers in 2.5 version of the Validator
This commit is contained in:
parent
a9af6be54e
commit
ce04073b4e
@ -18,6 +18,7 @@ use Symfony\Component\Validator\ConstraintViolationList;
|
||||
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;
|
||||
@ -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.
|
||||
*
|
||||
@ -360,4 +368,36 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
{
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
@ -678,4 +678,52 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
@ -238,6 +238,52 @@ abstract class AbstractLegacyApiTest extends AbstractValidatorTest
|
||||
$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());
|
||||
|
@ -28,10 +28,10 @@ class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,10 @@ class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,10 @@ use Symfony\Component\Validator\Validator\RecursiveValidator;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ use Symfony\Component\Validator\Validator as LegacyValidator;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,16 +129,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
|
||||
|
@ -27,6 +27,7 @@ use Symfony\Component\Validator\Mapping\MetadataInterface;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -432,6 +440,14 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
||||
{
|
||||
$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
|
||||
|
@ -15,6 +15,7 @@ use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
|
||||
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}.
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,7 +73,8 @@ class RecursiveValidator implements ValidatorInterface
|
||||
return new RecursiveContextualValidator(
|
||||
$this->contextFactory->createContext($this, $root),
|
||||
$this->metadataFactory,
|
||||
$this->validatorFactory
|
||||
$this->validatorFactory,
|
||||
$this->objectInitializers
|
||||
);
|
||||
}
|
||||
|
||||
@ -77,7 +86,8 @@ class RecursiveValidator implements ValidatorInterface
|
||||
return new RecursiveContextualValidator(
|
||||
$context,
|
||||
$this->metadataFactory,
|
||||
$this->validatorFactory
|
||||
$this->validatorFactory,
|
||||
$this->objectInitializers
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -411,9 +411,9 @@ class ValidatorBuilder implements ValidatorBuilderInterface
|
||||
$contextFactory = new LegacyExecutionContextFactory($metadataFactory, $translator, $this->translationDomain);
|
||||
|
||||
if (Validation::API_VERSION_2_5 === $apiVersion) {
|
||||
return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory);
|
||||
return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory, $this->initializers);
|
||||
}
|
||||
|
||||
return new LegacyValidator($contextFactory, $metadataFactory, $validatorFactory);
|
||||
return new LegacyValidator($contextFactory, $metadataFactory, $validatorFactory, $this->initializers);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user