[Validator] Added a recursive clone of the new implementation for speed comparison
This commit is contained in:
parent
f61d31e5fa
commit
23534ca6ab
@ -17,6 +17,7 @@ use Symfony\Component\Validator\ConstraintViolation;
|
||||
use Symfony\Component\Validator\ConstraintViolationList;
|
||||
use Symfony\Component\Validator\Exception\BadMethodCallException;
|
||||
use Symfony\Component\Validator\Group\GroupManagerInterface;
|
||||
use Symfony\Component\Validator\Mapping\MetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
use Symfony\Component\Validator\Util\PropertyPath;
|
||||
@ -48,11 +49,6 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
*/
|
||||
private $root;
|
||||
|
||||
/**
|
||||
* @var GroupManagerInterface
|
||||
*/
|
||||
private $groupManager;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
@ -71,11 +67,32 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
private $violations;
|
||||
|
||||
/**
|
||||
* The current node under validation.
|
||||
* The currently validated value.
|
||||
*
|
||||
* @var Node
|
||||
* @var mixed
|
||||
*/
|
||||
private $node;
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* The property path leading to the current value.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $propertyPath = '';
|
||||
|
||||
/**
|
||||
* The current validation metadata.
|
||||
*
|
||||
* @var MetadataInterface
|
||||
*/
|
||||
private $metadata;
|
||||
|
||||
/**
|
||||
* The currently validated group.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $group;
|
||||
|
||||
/**
|
||||
* Stores which objects have been validated in which group.
|
||||
@ -104,9 +121,6 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
* @param ValidatorInterface $validator The validator
|
||||
* @param mixed $root The root value of the
|
||||
* validated object graph
|
||||
* @param GroupManagerInterface $groupManager The manager for accessing
|
||||
* the currently validated
|
||||
* group
|
||||
* @param TranslatorInterface $translator The translator
|
||||
* @param string|null $translationDomain The translation domain to
|
||||
* use for translating
|
||||
@ -115,24 +129,45 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
* @internal Called by {@link ExecutionContextFactory}. Should not be used
|
||||
* in user code.
|
||||
*/
|
||||
public function __construct(ValidatorInterface $validator, $root, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null)
|
||||
public function __construct(ValidatorInterface $validator, $root, TranslatorInterface $translator, $translationDomain = null)
|
||||
{
|
||||
$this->validator = $validator;
|
||||
$this->root = $root;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->translator = $translator;
|
||||
$this->translationDomain = $translationDomain;
|
||||
$this->violations = new ConstraintViolationList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the values of the context to match the given node.
|
||||
*
|
||||
* @param Node $node The currently validated node
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setCurrentNode(Node $node)
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->node = $node;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMetadata(MetadataInterface $metadata = null)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPropertyPath($propertyPath)
|
||||
{
|
||||
$this->propertyPath = (string) $propertyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setGroup($group)
|
||||
{
|
||||
$this->group = $group;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -209,7 +244,7 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->node ? $this->node->value : null;
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -217,7 +252,7 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
*/
|
||||
public function getMetadata()
|
||||
{
|
||||
return $this->node ? $this->node->metadata : null;
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,7 +260,7 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
*/
|
||||
public function getGroup()
|
||||
{
|
||||
return $this->groupManager->getCurrentGroup();
|
||||
return $this->group;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,9 +268,7 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
*/
|
||||
public function getClassName()
|
||||
{
|
||||
$metadata = $this->getMetadata();
|
||||
|
||||
return $metadata instanceof ClassBasedInterface ? $metadata->getClassName() : null;
|
||||
return $this->metadata instanceof ClassBasedInterface ? $this->metadata->getClassName() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -243,9 +276,7 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
*/
|
||||
public function getPropertyName()
|
||||
{
|
||||
$metadata = $this->getMetadata();
|
||||
|
||||
return $metadata instanceof PropertyMetadataInterface ? $metadata->getPropertyName() : null;
|
||||
return $this->metadata instanceof PropertyMetadataInterface ? $this->metadata->getPropertyName() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -253,9 +284,7 @@ class ExecutionContext implements ExecutionContextInterface
|
||||
*/
|
||||
public function getPropertyPath($subPath = '')
|
||||
{
|
||||
$propertyPath = $this->node ? $this->node->propertyPath : '';
|
||||
|
||||
return PropertyPath::append($propertyPath, $subPath);
|
||||
return PropertyPath::append($this->propertyPath, $subPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,11 +26,6 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
*/
|
||||
class ExecutionContextFactory implements ExecutionContextFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @var GroupManagerInterface
|
||||
*/
|
||||
private $groupManager;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
@ -44,17 +39,13 @@ class ExecutionContextFactory implements ExecutionContextFactoryInterface
|
||||
/**
|
||||
* Creates a new context factory.
|
||||
*
|
||||
* @param GroupManagerInterface $groupManager The manager for accessing
|
||||
* the currently validated
|
||||
* group
|
||||
* @param TranslatorInterface $translator The translator
|
||||
* @param string|null $translationDomain The translation domain to
|
||||
* use for translating
|
||||
* violation messages
|
||||
*/
|
||||
public function __construct(GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null)
|
||||
public function __construct(TranslatorInterface $translator, $translationDomain = null)
|
||||
{
|
||||
$this->groupManager = $groupManager;
|
||||
$this->translator = $translator;
|
||||
$this->translationDomain = $translationDomain;
|
||||
}
|
||||
@ -67,7 +58,6 @@ class ExecutionContextFactory implements ExecutionContextFactoryInterface
|
||||
return new ExecutionContext(
|
||||
$validator,
|
||||
$root,
|
||||
$this->groupManager,
|
||||
$this->translator,
|
||||
$this->translationDomain
|
||||
);
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Validator\Context;
|
||||
|
||||
use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Mapping\MetadataInterface;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
|
||||
@ -100,14 +101,44 @@ interface ExecutionContextInterface extends LegacyExecutionContextInterface
|
||||
public function getValidator();
|
||||
|
||||
/**
|
||||
* Sets the currently traversed node.
|
||||
* Sets the currently validated value.
|
||||
*
|
||||
* @param Node $node The current node
|
||||
* @param mixed $value The validated value
|
||||
*
|
||||
* @internal Used by the validator engine. Should not be called by user
|
||||
* code.
|
||||
*/
|
||||
public function setCurrentNode(Node $node);
|
||||
public function setValue($value);
|
||||
|
||||
/**
|
||||
* Sets the current validation metadata.
|
||||
*
|
||||
* @param MetadataInterface $metadata The validation metadata
|
||||
*
|
||||
* @internal Used by the validator engine. Should not be called by user
|
||||
* code.
|
||||
*/
|
||||
public function setMetadata(MetadataInterface $metadata = null);
|
||||
|
||||
/**
|
||||
* Sets the property path leading to the current value.
|
||||
*
|
||||
* @param string $propertyPath The property path to the current value
|
||||
*
|
||||
* @internal Used by the validator engine. Should not be called by user
|
||||
* code.
|
||||
*/
|
||||
public function setPropertyPath($propertyPath);
|
||||
|
||||
/**
|
||||
* Sets the currently validated group.
|
||||
*
|
||||
* @param string|null $group The validated group
|
||||
*
|
||||
* @internal Used by the validator engine. Should not be called by user
|
||||
* code.
|
||||
*/
|
||||
public function setGroup($group);
|
||||
|
||||
/**
|
||||
* Marks an object as validated in a specific validation group.
|
||||
|
@ -42,7 +42,7 @@ class LegacyExecutionContext extends ExecutionContext
|
||||
* @internal Called by {@link LegacyExecutionContextFactory}. Should not be used
|
||||
* in user code.
|
||||
*/
|
||||
public function __construct(ValidatorInterface $validator, $root, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null)
|
||||
public function __construct(ValidatorInterface $validator, $root, TranslatorInterface $translator, $translationDomain = null)
|
||||
{
|
||||
if (!$validator instanceof LegacyValidatorInterface) {
|
||||
throw new InvalidArgumentException(
|
||||
@ -54,7 +54,6 @@ class LegacyExecutionContext extends ExecutionContext
|
||||
parent::__construct(
|
||||
$validator,
|
||||
$root,
|
||||
$groupManager,
|
||||
$translator,
|
||||
$translationDomain
|
||||
);
|
||||
|
@ -26,11 +26,6 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
*/
|
||||
class LegacyExecutionContextFactory implements ExecutionContextFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @var GroupManagerInterface
|
||||
*/
|
||||
private $groupManager;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
@ -44,17 +39,13 @@ class LegacyExecutionContextFactory implements ExecutionContextFactoryInterface
|
||||
/**
|
||||
* Creates a new context factory.
|
||||
*
|
||||
* @param GroupManagerInterface $groupManager The manager for accessing
|
||||
* the currently validated
|
||||
* group
|
||||
* @param TranslatorInterface $translator The translator
|
||||
* @param string|null $translationDomain The translation domain to
|
||||
* use for translating
|
||||
* violation messages
|
||||
*/
|
||||
public function __construct(GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null)
|
||||
public function __construct(TranslatorInterface $translator, $translationDomain = null)
|
||||
{
|
||||
$this->groupManager = $groupManager;
|
||||
$this->translator = $translator;
|
||||
$this->translationDomain = $translationDomain;
|
||||
}
|
||||
@ -67,7 +58,6 @@ class LegacyExecutionContextFactory implements ExecutionContextFactoryInterface
|
||||
return new LegacyExecutionContext(
|
||||
$validator,
|
||||
$root,
|
||||
$this->groupManager,
|
||||
$this->translator,
|
||||
$this->translationDomain
|
||||
);
|
||||
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Group;
|
||||
|
||||
/**
|
||||
* Returns the group that is currently being validated.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface GroupManagerInterface
|
||||
{
|
||||
/**
|
||||
* Returns the group that is currently being validated.
|
||||
*
|
||||
* @return string|null The current group or null, if no validation is
|
||||
* active.
|
||||
*/
|
||||
public function getCurrentGroup();
|
||||
}
|
@ -30,6 +30,8 @@ class ContextUpdateVisitor extends AbstractVisitor
|
||||
*/
|
||||
public function visit(Node $node, ExecutionContextInterface $context)
|
||||
{
|
||||
$context->setCurrentNode($node);
|
||||
$context->setValue($node->value);
|
||||
$context->setMetadata($node->metadata);
|
||||
$context->setPropertyPath($node->propertyPath);
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ namespace Symfony\Component\Validator\NodeVisitor;
|
||||
use Symfony\Component\Validator\Constraints\GroupSequence;
|
||||
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Group\GroupManagerInterface;
|
||||
use Symfony\Component\Validator\Node\ClassNode;
|
||||
use Symfony\Component\Validator\Node\CollectionNode;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
@ -27,7 +26,7 @@ use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class NodeValidationVisitor extends AbstractVisitor implements GroupManagerInterface
|
||||
class NodeValidationVisitor extends AbstractVisitor
|
||||
{
|
||||
/**
|
||||
* @var ConstraintValidatorFactoryInterface
|
||||
@ -39,13 +38,6 @@ class NodeValidationVisitor extends AbstractVisitor implements GroupManagerInter
|
||||
*/
|
||||
private $nodeTraverser;
|
||||
|
||||
/**
|
||||
* The currently validated group.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $currentGroup;
|
||||
|
||||
/**
|
||||
* Creates a new visitor.
|
||||
*
|
||||
@ -128,14 +120,6 @@ class NodeValidationVisitor extends AbstractVisitor implements GroupManagerInter
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCurrentGroup()
|
||||
{
|
||||
return $this->currentGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a node's value in each group of a group sequence.
|
||||
*
|
||||
@ -181,7 +165,7 @@ class NodeValidationVisitor extends AbstractVisitor implements GroupManagerInter
|
||||
private function validateNodeForGroup(Node $node, $group, ExecutionContextInterface $context, $objectHash)
|
||||
{
|
||||
try {
|
||||
$this->currentGroup = $group;
|
||||
$context->setGroup($group);
|
||||
|
||||
foreach ($node->metadata->findConstraints($group) as $constraint) {
|
||||
// Prevent duplicate validation of constraints, in the case
|
||||
@ -211,10 +195,10 @@ class NodeValidationVisitor extends AbstractVisitor implements GroupManagerInter
|
||||
$validator->validate($node->value, $constraint);
|
||||
}
|
||||
|
||||
$this->currentGroup = null;
|
||||
$context->setGroup(null);
|
||||
} catch (\Exception $e) {
|
||||
// Should be put into a finally block once we switch to PHP 5.5
|
||||
$this->currentGroup = null;
|
||||
$context->setGroup(null);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Tests\Context;
|
||||
|
||||
use Symfony\Component\Validator\Context\ExecutionContext;
|
||||
|
||||
/**
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ExecutionContextTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
const ROOT = '__ROOT__';
|
||||
|
||||
const TRANSLATION_DOMAIN = '__TRANSLATION_DOMAIN__';
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $validator;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $groupManager;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @var ExecutionContext
|
||||
*/
|
||||
private $context;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '5.3.9', '<')) {
|
||||
$this->markTestSkipped('Not supported prior to PHP 5.3.9');
|
||||
}
|
||||
|
||||
$this->validator = $this->getMock('Symfony\Component\Validator\Validator\ValidatorInterface');
|
||||
$this->groupManager = $this->getMock('Symfony\Component\Validator\Group\GroupManagerInterface');
|
||||
$this->translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface');
|
||||
|
||||
$this->context = new ExecutionContext(
|
||||
$this->validator, self::ROOT, $this->groupManager, $this->translator, self::TRANSLATION_DOMAIN
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetGroup()
|
||||
{
|
||||
$this->groupManager->expects($this->once())
|
||||
->method('getCurrentGroup')
|
||||
->will($this->returnValue('Current Group'));
|
||||
|
||||
$this->assertSame('Current Group', $this->context->getGroup());
|
||||
}
|
||||
}
|
@ -34,17 +34,8 @@ class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||
|
||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||
$contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
||||
$validator = new LegacyValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
$groupSequenceResolver = new DefaultGroupReplacingVisitor();
|
||||
$contextRefresher = new ContextUpdateVisitor();
|
||||
$contextFactory = new LegacyExecutionContextFactory(new DefaultTranslator());
|
||||
|
||||
$nodeTraverser->addVisitor($groupSequenceResolver);
|
||||
$nodeTraverser->addVisitor($contextRefresher);
|
||||
$nodeTraverser->addVisitor($nodeValidator);
|
||||
|
||||
return $validator;
|
||||
return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory());
|
||||
}
|
||||
}
|
||||
|
@ -34,17 +34,8 @@ class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest
|
||||
|
||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||
$contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
||||
$validator = new LegacyValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
$groupSequenceResolver = new DefaultGroupReplacingVisitor();
|
||||
$contextRefresher = new ContextUpdateVisitor();
|
||||
$contextFactory = new LegacyExecutionContextFactory(new DefaultTranslator());
|
||||
|
||||
$nodeTraverser->addVisitor($groupSequenceResolver);
|
||||
$nodeTraverser->addVisitor($contextRefresher);
|
||||
$nodeTraverser->addVisitor($nodeValidator);
|
||||
|
||||
return $validator;
|
||||
return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Tests\Validator;
|
||||
|
||||
use Symfony\Component\Validator\DefaultTranslator;
|
||||
use Symfony\Component\Validator\ConstraintValidatorFactory;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextFactory;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\NodeVisitor\ContextUpdateVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\DefaultGroupReplacingVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||
use Symfony\Component\Validator\Validator\RecursiveValidator;
|
||||
use Symfony\Component\Validator\Validator\TraversingValidator;
|
||||
|
||||
class RecursiveValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||
{
|
||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$contextFactory = new ExecutionContextFactory(new DefaultTranslator());
|
||||
|
||||
return new RecursiveValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory());
|
||||
}
|
||||
}
|
@ -19,18 +19,19 @@ use Symfony\Component\Validator\NodeVisitor\ContextUpdateVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\DefaultGroupReplacingVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||
use Symfony\Component\Validator\Validator\Validator;
|
||||
use Symfony\Component\Validator\Validator\TraversingValidator;
|
||||
|
||||
class Validator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||
class TraversingValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||
{
|
||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||
$contextFactory = new ExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
||||
$validator = new Validator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
$contextFactory = new ExecutionContextFactory(new DefaultTranslator());
|
||||
$validator = new TraversingValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
|
||||
$groupSequenceResolver = new DefaultGroupReplacingVisitor();
|
||||
$contextRefresher = new ContextUpdateVisitor();
|
||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||
|
||||
$nodeTraverser->addVisitor($groupSequenceResolver);
|
||||
$nodeTraverser->addVisitor($contextRefresher);
|
@ -130,7 +130,7 @@ class ValidatorBuilderTest extends \PHPUnit_Framework_TestCase
|
||||
public function testSetApiVersion25()
|
||||
{
|
||||
$this->assertSame($this->builder, $this->builder->setApiVersion(Validation::API_VERSION_2_5));
|
||||
$this->assertInstanceOf('Symfony\Component\Validator\Validator\Validator', $this->builder->getValidator());
|
||||
$this->assertInstanceOf('Symfony\Component\Validator\Validator\TraversingValidator', $this->builder->getValidator());
|
||||
}
|
||||
|
||||
public function testSetApiVersion24And25()
|
||||
|
@ -22,7 +22,7 @@ use Symfony\Component\Translation\TranslatorInterface;
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
|
||||
* Use {@link Validator\Validator} instead.
|
||||
* Use {@link Validator\TraversingValidator} instead.
|
||||
*/
|
||||
class Validator implements ValidatorInterface
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface;
|
||||
* @deprecated Implemented for backwards compatibility with Symfony < 2.5.
|
||||
* To be removed in Symfony 3.0.
|
||||
*/
|
||||
class LegacyValidator extends Validator implements LegacyValidatorInterface
|
||||
class LegacyValidator extends RecursiveValidator implements LegacyValidatorInterface
|
||||
{
|
||||
public function validate($value, $groups = null, $traverse = false, $deep = false)
|
||||
{
|
||||
|
@ -0,0 +1,700 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Validator;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Constraints\GroupSequence;
|
||||
use Symfony\Component\Validator\Constraints\Valid;
|
||||
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
|
||||
use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
|
||||
use Symfony\Component\Validator\Exception\ValidatorException;
|
||||
use Symfony\Component\Validator\Mapping\CascadingStrategy;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\GenericMetadata;
|
||||
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\Node\ClassNode;
|
||||
use Symfony\Component\Validator\Node\CollectionNode;
|
||||
use Symfony\Component\Validator\Node\GenericNode;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
use Symfony\Component\Validator\Node\PropertyNode;
|
||||
use Symfony\Component\Validator\Util\PropertyPath;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ContextualValidatorInterface}.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class RecursiveContextualValidator implements ContextualValidatorInterface
|
||||
{
|
||||
/**
|
||||
* @var ExecutionContextInterface
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @var MetadataFactoryInterface
|
||||
*/
|
||||
private $metadataFactory;
|
||||
|
||||
private $validatorFactory;
|
||||
|
||||
private $currentGroup;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory)
|
||||
{
|
||||
$this->context = $context;
|
||||
$this->defaultPropertyPath = $context->getPropertyPath();
|
||||
$this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP);
|
||||
$this->metadataFactory = $metadataFactory;
|
||||
$this->validatorFactory = $validatorFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function atPath($path)
|
||||
{
|
||||
$this->defaultPropertyPath = $this->context->getPropertyPath($path);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, $constraints = null, $groups = null)
|
||||
{
|
||||
if (null === $constraints) {
|
||||
$constraints = array(new Valid());
|
||||
} elseif (!is_array($constraints)) {
|
||||
$constraints = array($constraints);
|
||||
}
|
||||
|
||||
$metadata = new GenericMetadata();
|
||||
$metadata->addConstraints($constraints);
|
||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||
|
||||
$this->traverseGenericNode(new GenericNode(
|
||||
$value,
|
||||
$metadata,
|
||||
$this->defaultPropertyPath,
|
||||
$groups
|
||||
), $this->context);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateProperty($object, $propertyName, $groups = null)
|
||||
{
|
||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||
|
||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||
throw new ValidatorException(sprintf(
|
||||
'The metadata factory should return instances of '.
|
||||
'"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||
'got: "%s".',
|
||||
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
||||
));
|
||||
}
|
||||
|
||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||
|
||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||
$propertyValue = $propertyMetadata->getPropertyValue($object);
|
||||
|
||||
$this->traverseGenericNode(new PropertyNode(
|
||||
$object,
|
||||
$propertyValue,
|
||||
$propertyMetadata,
|
||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||
$groups
|
||||
), $this->context);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
|
||||
{
|
||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||
|
||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||
throw new ValidatorException(sprintf(
|
||||
'The metadata factory should return instances of '.
|
||||
'"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||
'got: "%s".',
|
||||
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
||||
));
|
||||
}
|
||||
|
||||
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
|
||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||
|
||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||
$this->traverseGenericNode(new PropertyNode(
|
||||
$object,
|
||||
$value,
|
||||
$propertyMetadata,
|
||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||
$groups,
|
||||
$groups
|
||||
), $this->context);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViolations()
|
||||
{
|
||||
return $this->context->getViolations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the given group or list of groups to an array.
|
||||
*
|
||||
* @param mixed $groups The groups to normalize
|
||||
*
|
||||
* @return array A group array
|
||||
*/
|
||||
protected function normalizeGroups($groups)
|
||||
{
|
||||
if (is_array($groups)) {
|
||||
return $groups;
|
||||
}
|
||||
|
||||
return array($groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses a class node.
|
||||
*
|
||||
* At first, each visitor is invoked for this node. Then, unless any
|
||||
* of the visitors aborts the traversal by returning false, a property
|
||||
* node is put on the node stack for each constrained property of the class.
|
||||
* At last, if the class is traversable and should be traversed according
|
||||
* to the selected traversal strategy, a new collection node is put on the
|
||||
* stack.
|
||||
*
|
||||
* @param ClassNode $node The class node
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
*
|
||||
* @throws UnsupportedMetadataException If a property metadata does not
|
||||
* implement {@link PropertyMetadataInterface}
|
||||
*
|
||||
* @see ClassNode
|
||||
* @see PropertyNode
|
||||
* @see CollectionNode
|
||||
* @see TraversalStrategy
|
||||
*/
|
||||
private function traverseClassNode(ClassNode $node, ExecutionContextInterface $context)
|
||||
{
|
||||
if (false === $this->validateNode($node, $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 === count($node->groups)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($node->metadata->getConstrainedProperties() as $propertyName) {
|
||||
foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
|
||||
if (!$propertyMetadata instanceof PropertyMetadataInterface) {
|
||||
throw new UnsupportedMetadataException(sprintf(
|
||||
'The property metadata instances should implement '.
|
||||
'"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '.
|
||||
'got: "%s".',
|
||||
is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata)
|
||||
));
|
||||
}
|
||||
|
||||
$this->traverseGenericNode(new PropertyNode(
|
||||
$node->value,
|
||||
$propertyMetadata->getPropertyValue($node->value),
|
||||
$propertyMetadata,
|
||||
$node->propertyPath
|
||||
? $node->propertyPath.'.'.$propertyName
|
||||
: $propertyName,
|
||||
$node->groups,
|
||||
$node->cascadedGroups
|
||||
), $context);
|
||||
}
|
||||
}
|
||||
|
||||
$traversalStrategy = $node->traversalStrategy;
|
||||
|
||||
// If no specific traversal strategy was requested when this method
|
||||
// was called, use the traversal strategy of the class' metadata
|
||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
|
||||
// Keep the STOP_RECURSION flag, if it was set
|
||||
$traversalStrategy = $node->metadata->getTraversalStrategy()
|
||||
| ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
|
||||
}
|
||||
|
||||
// Traverse only if IMPLICIT or TRAVERSE
|
||||
if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If IMPLICIT, stop unless we deal with a Traversable
|
||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$node->value instanceof \Traversable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If TRAVERSE, the constructor will fail if we have no Traversable
|
||||
$this->traverseCollectionNode(new CollectionNode(
|
||||
$node->value,
|
||||
$node->propertyPath,
|
||||
$node->groups,
|
||||
$node->cascadedGroups,
|
||||
$traversalStrategy
|
||||
), $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses a collection node.
|
||||
*
|
||||
* At first, each visitor is invoked for this node. Then, unless any
|
||||
* of the visitors aborts the traversal by returning false, the successor
|
||||
* nodes of the collection node are put on the stack:
|
||||
*
|
||||
* - for each object in the collection with associated class metadata, a
|
||||
* new class node is put on the stack;
|
||||
* - if an object has no associated class metadata, but is traversable, and
|
||||
* unless the {@link TraversalStrategy::STOP_RECURSION} flag is set for
|
||||
* collection node, a new collection node is put on the stack for that
|
||||
* object;
|
||||
* - for each array in the collection, a new collection node is put on the
|
||||
* stack.
|
||||
*
|
||||
* @param CollectionNode $node The collection node
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
*
|
||||
* @see ClassNode
|
||||
* @see CollectionNode
|
||||
*/
|
||||
private function traverseCollectionNode(CollectionNode $node, ExecutionContextInterface $context)
|
||||
{
|
||||
$traversalStrategy = $node->traversalStrategy;
|
||||
|
||||
if ($traversalStrategy & TraversalStrategy::STOP_RECURSION) {
|
||||
$traversalStrategy = TraversalStrategy::NONE;
|
||||
} else {
|
||||
$traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||
}
|
||||
|
||||
foreach ($node->value as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
// Arrays are always cascaded, independent of the specified
|
||||
// traversal strategy
|
||||
// (BC with Symfony < 2.5)
|
||||
$this->traverseCollectionNode(new CollectionNode(
|
||||
$value,
|
||||
$node->propertyPath.'['.$key.']',
|
||||
$node->groups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
), $context);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scalar and null values in the collection are ignored
|
||||
// (BC with Symfony < 2.5)
|
||||
if (is_object($value)) {
|
||||
$this->cascadeObject(
|
||||
$value,
|
||||
$node->propertyPath.'['.$key.']',
|
||||
$node->groups,
|
||||
$traversalStrategy,
|
||||
$context
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses a node that is neither a class nor a collection node.
|
||||
*
|
||||
* At first, each visitor is invoked for this node. Then, unless any
|
||||
* of the visitors aborts the traversal by returning false, the successor
|
||||
* nodes of the collection node are put on the stack:
|
||||
*
|
||||
* - if the node contains an object with associated class metadata, a new
|
||||
* class node is put on the stack;
|
||||
* - if the node contains a traversable object without associated class
|
||||
* metadata and traversal is enabled according to the selected traversal
|
||||
* strategy, a collection node is put on the stack;
|
||||
* - if the node contains an array, a collection node is put on the stack.
|
||||
*
|
||||
* @param Node $node The node
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
*/
|
||||
private function traverseGenericNode(Node $node, ExecutionContextInterface $context)
|
||||
{
|
||||
if (false === $this->validateNode($node, $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $node->value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The "cascadedGroups" property is set by the NodeValidationVisitor when
|
||||
// traversing group sequences
|
||||
$cascadedGroups = null !== $node->cascadedGroups
|
||||
? $node->cascadedGroups
|
||||
: $node->groups;
|
||||
|
||||
if (0 === count($cascadedGroups)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cascadingStrategy = $node->metadata->getCascadingStrategy();
|
||||
$traversalStrategy = $node->traversalStrategy;
|
||||
|
||||
// If no specific traversal strategy was requested when this method
|
||||
// was called, use the traversal strategy of the node's metadata
|
||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
|
||||
// Keep the STOP_RECURSION flag, if it was set
|
||||
$traversalStrategy = $node->metadata->getTraversalStrategy()
|
||||
| ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
|
||||
}
|
||||
|
||||
if (is_array($node->value)) {
|
||||
// Arrays are always traversed, independent of the specified
|
||||
// traversal strategy
|
||||
// (BC with Symfony < 2.5)
|
||||
$this->traverseCollectionNode(new CollectionNode(
|
||||
$node->value,
|
||||
$node->propertyPath,
|
||||
$cascadedGroups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
), $context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cascadingStrategy & CascadingStrategy::CASCADE) {
|
||||
// If the value is a scalar, pass it anyway, because we want
|
||||
// a NoSuchMetadataException to be thrown in that case
|
||||
// (BC with Symfony < 2.5)
|
||||
$this->cascadeObject(
|
||||
$node->value,
|
||||
$node->propertyPath,
|
||||
$cascadedGroups,
|
||||
$traversalStrategy,
|
||||
$context
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Currently, the traversal strategy can only be TRAVERSE for a
|
||||
// generic node if the cascading strategy is CASCADE. Thus, traversable
|
||||
// objects will always be handled within cascadeObject() and there's
|
||||
// nothing more to do here.
|
||||
|
||||
// see GenericMetadata::addConstraint()
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the cascading logic for an object.
|
||||
*
|
||||
* If class metadata is available for the object, a class node is put on
|
||||
* the node stack. Otherwise, if the selected traversal strategy allows
|
||||
* traversal of the object, a new collection node is put on the stack.
|
||||
* Otherwise, an exception is thrown.
|
||||
*
|
||||
* @param object $object The object to cascade
|
||||
* @param string $propertyPath The current property path
|
||||
* @param string[] $groups The validated groups
|
||||
* @param integer $traversalStrategy The strategy for traversing the
|
||||
* cascaded object
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
*
|
||||
* @throws NoSuchMetadataException If the object has no associated metadata
|
||||
* and does not implement {@link \Traversable}
|
||||
* or if traversal is disabled via the
|
||||
* $traversalStrategy argument
|
||||
* @throws UnsupportedMetadataException If the metadata returned by the
|
||||
* metadata factory does not implement
|
||||
* {@link ClassMetadataInterface}
|
||||
*/
|
||||
private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context)
|
||||
{
|
||||
try {
|
||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||
|
||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||
throw new UnsupportedMetadataException(sprintf(
|
||||
'The metadata factory should return instances of '.
|
||||
'"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||
'got: "%s".',
|
||||
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
||||
));
|
||||
}
|
||||
|
||||
$this->traverseClassNode(new ClassNode(
|
||||
$object,
|
||||
$classMetadata,
|
||||
$propertyPath,
|
||||
$groups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
), $context);
|
||||
} catch (NoSuchMetadataException $e) {
|
||||
// Rethrow if not Traversable
|
||||
if (!$object instanceof \Traversable) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Rethrow unless IMPLICIT or TRAVERSE
|
||||
if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->traverseCollectionNode(new CollectionNode(
|
||||
$object,
|
||||
$propertyPath,
|
||||
$groups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
), $context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a node's value against the constraints defined in the node's
|
||||
* metadata.
|
||||
*
|
||||
* Objects and constraints that were validated before in the same context
|
||||
* will be skipped.
|
||||
*
|
||||
* @param Node $node The current node
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
*
|
||||
* @return Boolean Whether to traverse the successor nodes
|
||||
*/
|
||||
public function validateNode(Node $node, ExecutionContextInterface $context)
|
||||
{
|
||||
if ($node instanceof CollectionNode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$context->setValue($node->value);
|
||||
$context->setMetadata($node->metadata);
|
||||
$context->setPropertyPath($node->propertyPath);
|
||||
|
||||
if ($node instanceof ClassNode) {
|
||||
$groupSequence = null;
|
||||
|
||||
if ($node->metadata->hasGroupSequence()) {
|
||||
// The group sequence is statically defined for the class
|
||||
$groupSequence = $node->metadata->getGroupSequence();
|
||||
} elseif ($node->metadata->isGroupSequenceProvider()) {
|
||||
// The group sequence is dynamically obtained from the validated
|
||||
// object
|
||||
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
|
||||
$groupSequence = $node->value->getGroupSequence();
|
||||
|
||||
if (!$groupSequence instanceof GroupSequence) {
|
||||
$groupSequence = new GroupSequence($groupSequence);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $groupSequence) {
|
||||
$key = array_search(Constraint::DEFAULT_GROUP, $node->groups);
|
||||
|
||||
if (false !== $key) {
|
||||
// Replace the "Default" group by the group sequence
|
||||
$node->groups[$key] = $groupSequence;
|
||||
|
||||
// Cascade the "Default" group when validating the sequence
|
||||
$groupSequence->cascadedGroup = Constraint::DEFAULT_GROUP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($node instanceof ClassNode) {
|
||||
$objectHash = spl_object_hash($node->value);
|
||||
} elseif ($node instanceof PropertyNode) {
|
||||
$objectHash = spl_object_hash($node->object);
|
||||
} else {
|
||||
$objectHash = null;
|
||||
}
|
||||
|
||||
// if group (=[<G1,G2>,G3,G4]) contains group sequence (=<G1,G2>)
|
||||
// then call traverse() with each entry of the group sequence and abort
|
||||
// if necessary (G1, G2)
|
||||
// finally call traverse() with remaining entries ([G3,G4]) or
|
||||
// simply continue traversal (if possible)
|
||||
|
||||
foreach ($node->groups as $key => $group) {
|
||||
// Even if we remove the following clause, the constraints on an
|
||||
// object won't be validated again due to the measures taken in
|
||||
// validateNodeForGroup().
|
||||
// The following shortcut, however, prevents validatedNodeForGroup()
|
||||
// from being called at all and enhances performance a bit.
|
||||
if ($node instanceof ClassNode) {
|
||||
// Use the object hash for group sequences
|
||||
$groupHash = is_object($group) ? spl_object_hash($group) : $group;
|
||||
|
||||
if ($context->isObjectValidatedForGroup($objectHash, $groupHash)) {
|
||||
// Skip this group when validating the successor nodes
|
||||
// (property and/or collection nodes)
|
||||
unset($node->groups[$key]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$context->markObjectAsValidatedForGroup($objectHash, $groupHash);
|
||||
}
|
||||
|
||||
// Validate normal group
|
||||
if (!$group instanceof GroupSequence) {
|
||||
$this->validateNodeForGroup($node, $group, $context, $objectHash);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Traverse group sequence until a violation is generated
|
||||
$this->stepThroughGroupSequence($node, $group, $context);
|
||||
|
||||
// Skip the group sequence when validating successor nodes
|
||||
unset($node->groups[$key]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a node's value in each group of a group sequence.
|
||||
*
|
||||
* If any of the groups' constraints generates a violation, subsequent
|
||||
* groups are not validated anymore.
|
||||
*
|
||||
* @param Node $node The validated node
|
||||
* @param GroupSequence $groupSequence The group sequence
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
*/
|
||||
private function stepThroughGroupSequence(Node $node, GroupSequence $groupSequence, ExecutionContextInterface $context)
|
||||
{
|
||||
$violationCount = count($context->getViolations());
|
||||
|
||||
foreach ($groupSequence->groups as $groupInSequence) {
|
||||
$node = clone $node;
|
||||
$node->groups = array($groupInSequence);
|
||||
|
||||
if (null !== $groupSequence->cascadedGroup) {
|
||||
$node->cascadedGroups = array($groupSequence->cascadedGroup);
|
||||
}
|
||||
|
||||
if ($node instanceof ClassNode) {
|
||||
$this->traverseClassNode($node, $context);
|
||||
} elseif ($node instanceof CollectionNode) {
|
||||
$this->traverseCollectionNode($node, $context);
|
||||
} else {
|
||||
$this->traverseGenericNode($node, $context);
|
||||
}
|
||||
|
||||
// Abort sequence validation if a violation was generated
|
||||
if (count($context->getViolations()) > $violationCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a node's value against all constraints in the given group.
|
||||
*
|
||||
* @param Node $node The validated node
|
||||
* @param string $group The group to validate
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
* @param string $objectHash The hash of the node's
|
||||
* object (if any)
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function validateNodeForGroup(Node $node, $group, ExecutionContextInterface $context, $objectHash)
|
||||
{
|
||||
try {
|
||||
$context->setGroup($group);
|
||||
|
||||
foreach ($node->metadata->findConstraints($group) as $constraint) {
|
||||
// Prevent duplicate validation of constraints, in the case
|
||||
// that constraints belong to multiple validated groups
|
||||
if (null !== $objectHash) {
|
||||
$constraintHash = spl_object_hash($constraint);
|
||||
|
||||
if ($node instanceof ClassNode) {
|
||||
if ($context->isClassConstraintValidated($objectHash, $constraintHash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$context->markClassConstraintAsValidated($objectHash, $constraintHash);
|
||||
} elseif ($node instanceof PropertyNode) {
|
||||
$propertyName = $node->metadata->getPropertyName();
|
||||
|
||||
if ($context->isPropertyConstraintValidated($objectHash, $propertyName, $constraintHash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$context->markPropertyConstraintAsValidated($objectHash, $propertyName, $constraintHash);
|
||||
}
|
||||
}
|
||||
|
||||
$validator = $this->validatorFactory->getInstance($constraint);
|
||||
$validator->initialize($context);
|
||||
$validator->validate($node->value, $constraint);
|
||||
}
|
||||
|
||||
$context->setGroup(null);
|
||||
} catch (\Exception $e) {
|
||||
// Should be put into a finally block once we switch to PHP 5.5
|
||||
$context->setGroup(null);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCurrentGroup()
|
||||
{
|
||||
return $this->currentGroup;
|
||||
}
|
||||
}
|
124
src/Symfony/Component/Validator/Validator/RecursiveValidator.php
Normal file
124
src/Symfony/Component/Validator/Validator/RecursiveValidator.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Validator\Validator;
|
||||
|
||||
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ValidatorInterface}.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class RecursiveValidator implements ValidatorInterface
|
||||
{
|
||||
/**
|
||||
* @var ExecutionContextFactoryInterface
|
||||
*/
|
||||
protected $contextFactory;
|
||||
|
||||
/**
|
||||
* @var MetadataFactoryInterface
|
||||
*/
|
||||
protected $metadataFactory;
|
||||
|
||||
protected $validatorFactory;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function __construct(ExecutionContextFactoryInterface $contextFactory, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory)
|
||||
{
|
||||
$this->contextFactory = $contextFactory;
|
||||
$this->metadataFactory = $metadataFactory;
|
||||
$this->validatorFactory = $validatorFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function startContext($root = null)
|
||||
{
|
||||
return new RecursiveContextualValidator(
|
||||
$this->contextFactory->createContext($this, $root),
|
||||
$this->metadataFactory,
|
||||
$this->validatorFactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function inContext(ExecutionContextInterface $context)
|
||||
{
|
||||
return new RecursiveContextualValidator(
|
||||
$context,
|
||||
$this->metadataFactory,
|
||||
$this->validatorFactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMetadataFor($object)
|
||||
{
|
||||
return $this->metadataFactory->getMetadataFor($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasMetadataFor($object)
|
||||
{
|
||||
return $this->metadataFactory->hasMetadataFor($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, $constraints = null, $groups = null)
|
||||
{
|
||||
return $this->startContext($value)
|
||||
->validate($value, $constraints, $groups)
|
||||
->getViolations();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateProperty($object, $propertyName, $groups = null)
|
||||
{
|
||||
return $this->startContext($object)
|
||||
->validateProperty($object, $propertyName, $groups)
|
||||
->getViolations();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
|
||||
{
|
||||
return $this->startContext($object)
|
||||
->validatePropertyValue($object, $propertyName, $value, $groups)
|
||||
->getViolations();
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ use Symfony\Component\Validator\Util\PropertyPath;
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ContextualValidator implements ContextualValidatorInterface
|
||||
class TraversingContextualValidator implements ContextualValidatorInterface
|
||||
{
|
||||
/**
|
||||
* @var ExecutionContextInterface
|
@ -22,7 +22,7 @@ use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class Validator implements ValidatorInterface
|
||||
class TraversingValidator implements ValidatorInterface
|
||||
{
|
||||
/**
|
||||
* @var ExecutionContextFactoryInterface
|
||||
@ -61,7 +61,7 @@ class Validator implements ValidatorInterface
|
||||
*/
|
||||
public function startContext($root = null)
|
||||
{
|
||||
return new ContextualValidator(
|
||||
return new TraversingContextualValidator(
|
||||
$this->contextFactory->createContext($this, $root),
|
||||
$this->nodeTraverser,
|
||||
$this->metadataFactory
|
||||
@ -73,7 +73,7 @@ class Validator implements ValidatorInterface
|
||||
*/
|
||||
public function inContext(ExecutionContextInterface $context)
|
||||
{
|
||||
return new ContextualValidator(
|
||||
return new TraversingContextualValidator(
|
||||
$context,
|
||||
$this->nodeTraverser,
|
||||
$this->metadataFactory
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Validator;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextFactory;
|
||||
@ -37,7 +38,7 @@ use Symfony\Component\Validator\NodeVisitor\DefaultGroupReplacingVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\ObjectInitializationVisitor;
|
||||
use Symfony\Component\Validator\Validator as ValidatorV24;
|
||||
use Symfony\Component\Validator\Validator\Validator;
|
||||
use Symfony\Component\Validator\Validator\TraversingValidator;
|
||||
use Symfony\Component\Validator\Validator\LegacyValidator;
|
||||
|
||||
/**
|
||||
@ -373,6 +374,19 @@ class ValidatorBuilder implements ValidatorBuilderInterface
|
||||
|
||||
if ($this->annotationReader) {
|
||||
$loaders[] = new AnnotationLoader($this->annotationReader);
|
||||
|
||||
AnnotationRegistry::registerLoader(function ($class) {
|
||||
if (0 === strpos($class, __NAMESPACE__.'\\Constraints\\')) {
|
||||
$file = str_replace(__NAMESPACE__.'\\Constraints\\', __DIR__.'/Constraints/', $class).'.php';
|
||||
|
||||
if (is_file($file)) {
|
||||
require_once $file;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$loader = null;
|
||||
@ -401,26 +415,24 @@ class ValidatorBuilder implements ValidatorBuilderInterface
|
||||
return new ValidatorV24($metadataFactory, $validatorFactory, $translator, $this->translationDomain, $this->initializers);
|
||||
}
|
||||
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, $validatorFactory);
|
||||
|
||||
if (Validation::API_VERSION_2_5 === $apiVersion) {
|
||||
$contextFactory = new ExecutionContextFactory($nodeValidator, $translator, $this->translationDomain);
|
||||
$contextFactory = new ExecutionContextFactory($translator, $this->translationDomain);
|
||||
} else {
|
||||
$contextFactory = new LegacyExecutionContextFactory($nodeValidator, $translator, $this->translationDomain);
|
||||
$contextFactory = new LegacyExecutionContextFactory($translator, $this->translationDomain);
|
||||
}
|
||||
|
||||
$nodeTraverser->addVisitor(new ContextUpdateVisitor());
|
||||
if (count($this->initializers) > 0) {
|
||||
$nodeTraverser->addVisitor(new ObjectInitializationVisitor($this->initializers));
|
||||
}
|
||||
$nodeTraverser->addVisitor(new DefaultGroupReplacingVisitor());
|
||||
$nodeTraverser->addVisitor($nodeValidator);
|
||||
|
||||
if (Validation::API_VERSION_2_5 === $apiVersion) {
|
||||
return new Validator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
if (count($this->initializers) > 0) {
|
||||
$nodeTraverser->addVisitor(new ObjectInitializationVisitor($this->initializers));
|
||||
}
|
||||
$nodeTraverser->addVisitor(new ContextUpdateVisitor());
|
||||
$nodeTraverser->addVisitor(new DefaultGroupReplacingVisitor());
|
||||
$nodeTraverser->addVisitor(new NodeValidationVisitor($nodeTraverser, $validatorFactory));
|
||||
|
||||
return new TraversingValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
}
|
||||
|
||||
return new LegacyValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
return new LegacyValidator($contextFactory, $metadataFactory, $validatorFactory);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user