bug #11410 [Validator] Fixed object initializers in 2.5 version of the Validator (webmozart)
This PR was merged into the 2.5 branch.
Discussion
----------
[Validator] Fixed object initializers in 2.5 version of the Validator
| Q | A
| ------------- | ---
| Bug fix? | yes
| New feature? | no
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #11159
| License | MIT
| Doc PR | -
Object initializers were forgot in the 2.5 Validator API. This PR implements them.
A bug was fixed also in the old version which resulted in the initializers being called multiple times if an object was validated in multiple groups within the same validation run. This fix will be backported separately to older versions.
Commits
-------
ce04073
[Validator] Fixed object initializers in 2.5 version of the Validator
This commit is contained in:
commit
4c97420fef
@ -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,10 +129,8 @@ class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionCo
|
||||
return;
|
||||
}
|
||||
|
||||
// Remember validating this object before starting and possibly
|
||||
// traversing the object graph
|
||||
$this->validatedObjects[$hash][$group] = true;
|
||||
|
||||
// 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.');
|
||||
@ -141,6 +139,11 @@ class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionCo
|
||||
}
|
||||
}
|
||||
|
||||
// Remember validating this object before starting and possibly
|
||||
// traversing the object graph
|
||||
$this->validatedObjects[$hash][$group] = true;
|
||||
}
|
||||
|
||||
// Validate arrays recursively by default, otherwise every driver needs
|
||||
// to implement special handling for arrays.
|
||||
// https://github.com/symfony/symfony/issues/6246
|
||||
|
@ -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,6 +53,11 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
||||
*/
|
||||
private $validatorFactory;
|
||||
|
||||
/**
|
||||
* @var ObjectInitializerInterface[]
|
||||
*/
|
||||
private $objectInitializers;
|
||||
|
||||
/**
|
||||
* Creates a validator for the given context.
|
||||
*
|
||||
@ -61,14 +67,16 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
|
||||
* 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,6 +40,11 @@ class RecursiveValidator implements ValidatorInterface
|
||||
*/
|
||||
protected $validatorFactory;
|
||||
|
||||
/**
|
||||
* @var ObjectInitializerInterface[]
|
||||
*/
|
||||
protected $objectInitializers;
|
||||
|
||||
/**
|
||||
* Creates a new validator.
|
||||
*
|
||||
@ -49,12 +55,14 @@ class RecursiveValidator implements ValidatorInterface
|
||||
* 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