From df41974f31e8a4c14a4c4f1bd82ace0cf5cf755c Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 19 Feb 2014 20:10:51 +0100 Subject: [PATCH] [Validator] Changed context manager to context factory The current context is not stored anymore. Instead, it is passed around the traverser and the visitors. For this reason, validation can occur in multiple contexts at the same time. --- .../Validator/Context/ExecutionContext.php | 50 ++-- .../Context/ExecutionContextFactory.php | 72 ++++++ .../ExecutionContextFactoryInterface.php | 37 +++ .../Context/ExecutionContextManager.php | 215 ------------------ .../ExecutionContextManagerInterface.php | 86 ------- .../Context/LegacyExecutionContext.php | 13 +- .../Context/LegacyExecutionContextFactory.php | 75 ++++++ .../Context/LegacyExecutionContextManager.php | 36 --- .../Validator/NodeTraverser/NodeTraverser.php | 114 +++++----- .../NodeTraverser/NodeTraverserInterface.php | 7 +- .../Validator/NodeVisitor/AbstractVisitor.php | 9 +- .../NodeVisitor/ContextRefresher.php | 60 +++++ .../NodeVisitor/GroupSequenceResolver.php | 3 +- .../Validator/NodeVisitor/NodeValidator.php | 38 +--- .../NodeVisitor/NodeVisitorInterface.php | 9 +- .../NodeVisitor/ObjectInitializer.php | 3 +- .../Context/ExecutionContextManagerTest.php | 95 -------- .../Tests/Context/ExecutionContextTest.php | 6 +- .../Tests/Validator/Abstract2Dot5ApiTest.php | 18 +- .../Validator/LegacyValidator2Dot5ApiTest.php | 18 +- .../LegacyValidatorLegacyApiTest.php | 18 +- .../Tests/Validator/Validator2Dot5ApiTest.php | 18 +- .../Validator/Validator/AbstractValidator.php | 188 --------------- .../Validator/ContextualValidator.php | 199 ++++++++++------ .../ContextualValidatorInterface.php | 70 +++++- .../Validator/Validator/Validator.php | 111 +++++---- .../Validator/ValidatorInterface.php | 5 + 27 files changed, 668 insertions(+), 905 deletions(-) create mode 100644 src/Symfony/Component/Validator/Context/ExecutionContextFactory.php create mode 100644 src/Symfony/Component/Validator/Context/ExecutionContextFactoryInterface.php delete mode 100644 src/Symfony/Component/Validator/Context/ExecutionContextManager.php delete mode 100644 src/Symfony/Component/Validator/Context/ExecutionContextManagerInterface.php create mode 100644 src/Symfony/Component/Validator/Context/LegacyExecutionContextFactory.php delete mode 100644 src/Symfony/Component/Validator/Context/LegacyExecutionContextManager.php create mode 100644 src/Symfony/Component/Validator/NodeVisitor/ContextRefresher.php delete mode 100644 src/Symfony/Component/Validator/Tests/Context/ExecutionContextManagerTest.php delete mode 100644 src/Symfony/Component/Validator/Validator/AbstractValidator.php diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php index f96370288c..187de382fb 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -25,7 +25,7 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; /** - * The context used and created by {@link ExecutionContextManager}. + * The context used and created by {@link ExecutionContextFactory}. * * @since 2.5 * @author Bernhard Schussek @@ -34,6 +34,11 @@ use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; */ class ExecutionContext implements ExecutionContextInterface, LegacyExecutionContextInterface { + /** + * @var ValidatorInterface + */ + private $validator; + /** * The root value of the validated object graph. * @@ -41,6 +46,21 @@ class ExecutionContext implements ExecutionContextInterface, LegacyExecutionCont */ private $root; + /** + * @var GroupManagerInterface + */ + private $groupManager; + + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var string + */ + private $translationDomain; + /** * The violations generated in the current context. * @@ -62,32 +82,12 @@ class ExecutionContext implements ExecutionContextInterface, LegacyExecutionCont */ private $nodeStack; - /** - * @var ValidatorInterface - */ - private $validator; - - /** - * @var GroupManagerInterface - */ - private $groupManager; - - /** - * @var TranslatorInterface - */ - private $translator; - - /** - * @var string - */ - private $translationDomain; - /** * Creates a new execution context. * + * @param ValidatorInterface $validator The validator * @param mixed $root The root value of the * validated object graph - * @param ValidatorInterface $validator The validator * @param GroupManagerInterface $groupManager The manager for accessing * the currently validated * group @@ -96,13 +96,13 @@ class ExecutionContext implements ExecutionContextInterface, LegacyExecutionCont * use for translating * violation messages * - * @internal Called by {@link ExecutionContextManager}. Should not be used + * @internal Called by {@link ExecutionContextFactory}. Should not be used * in user code. */ - public function __construct($root, ValidatorInterface $validator, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null) + public function __construct(ValidatorInterface $validator, $root, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null) { - $this->root = $root; $this->validator = $validator; + $this->root = $root; $this->groupManager = $groupManager; $this->translator = $translator; $this->translationDomain = $translationDomain; diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php b/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php new file mode 100644 index 0000000000..3305e1a943 --- /dev/null +++ b/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Validator\Group\GroupManagerInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * Creates new {@link ExecutionContext} instances. + * + * @since 2.5 + * @author Bernhard Schussek + */ +class ExecutionContextFactory implements ExecutionContextFactoryInterface +{ + /** + * @var GroupManagerInterface + */ + private $groupManager; + + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var string|null + */ + private $translationDomain; + + /** + * 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) + { + $this->groupManager = $groupManager; + $this->translator = $translator; + $this->translationDomain = $translationDomain; + } + + /** + * {@inheritdoc} + */ + public function createContext(ValidatorInterface $validator, $root) + { + return new ExecutionContext( + $validator, + $root, + $this->groupManager, + $this->translator, + $this->translationDomain + ); + } +} diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextFactoryInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextFactoryInterface.php new file mode 100644 index 0000000000..f0ee00174f --- /dev/null +++ b/src/Symfony/Component/Validator/Context/ExecutionContextFactoryInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * Creates instances of {@link ExecutionContextInterface}. + * + * You can use a custom factory if you want to customize the execution context + * that is passed through the validation run. + * + * @since 2.5 + * @author Bernhard Schussek + */ +interface ExecutionContextFactoryInterface +{ + /** + * Creates a new execution context. + * + * @param ValidatorInterface $validator The validator + * @param mixed $root The root value of the validated + * object graph + * + * @return ExecutionContextInterface The new execution context + */ + public function createContext(ValidatorInterface $validator, $root); +} diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextManager.php b/src/Symfony/Component/Validator/Context/ExecutionContextManager.php deleted file mode 100644 index 6214eabf46..0000000000 --- a/src/Symfony/Component/Validator/Context/ExecutionContextManager.php +++ /dev/null @@ -1,215 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Validator\Context; - -use Symfony\Component\Translation\TranslatorInterface; -use Symfony\Component\Validator\Exception\RuntimeException; -use Symfony\Component\Validator\Group\GroupManagerInterface; -use Symfony\Component\Validator\Node\Node; -use Symfony\Component\Validator\NodeVisitor\AbstractVisitor; -use Symfony\Component\Validator\Validator\ValidatorInterface; - -/** - * The default implementation of {@link ExecutionContextManagerInterface}. - * - * This class implements {@link \Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface} - * and updates the current context with the current node of the validation - * traversal. - * - * After creating a new instance, the method {@link initialize()} must be - * called with a {@link ValidatorInterface} instance. Calling methods such as - * {@link startContext()} or {@link enterNode()} without initializing the - * manager first will lead to errors. - * - * @since 2.5 - * @author Bernhard Schussek - * - * @see ExecutionContextManagerInterface - * @see \Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface - */ -class ExecutionContextManager extends AbstractVisitor implements ExecutionContextManagerInterface -{ - /** - * @var GroupManagerInterface - */ - private $groupManager; - - /** - * @var ValidatorInterface - */ - private $validator; - - /** - * @var ExecutionContext - */ - private $currentContext; - - /** - * @var \SplStack|ExecutionContext[] - */ - private $contextStack; - - /** - * @var TranslatorInterface - */ - private $translator; - - /** - * @var string|null - */ - private $translationDomain; - - /** - * Creates a new context manager. - * - * @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) - { - $this->groupManager = $groupManager; - $this->translator = $translator; - $this->translationDomain = $translationDomain; - $this->contextStack = new \SplStack(); - } - - /** - * Initializes the manager with a validator. - * - * @param ValidatorInterface $validator The validator - */ - public function initialize(ValidatorInterface $validator) - { - $this->validator = $validator; - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException If {@link initialize()} wasn't called - */ - public function startContext($root) - { - if (null === $this->validator) { - throw new RuntimeException( - 'initialize() must be called before startContext().' - ); - } - - $this->currentContext = $this->createContext( - $root, - $this->validator, - $this->groupManager, - $this->translator, - $this->translationDomain - ); - - $this->contextStack->push($this->currentContext); - - return $this->currentContext; - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException If {@link startContext()} wasn't called - */ - public function stopContext() - { - if (0 === count($this->contextStack)) { - throw new RuntimeException( - 'No context was started yet. Call startContext() before '. - 'stopContext().' - ); - } - - // Remove the current context from the stack - $stoppedContext = $this->contextStack->pop(); - - // Adjust the current context to the previous context - $this->currentContext = count($this->contextStack) > 0 - ? $this->contextStack->top() - : null; - - return $stoppedContext; - } - - /** - * {@inheritdoc} - */ - public function getCurrentContext() - { - return $this->currentContext; - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException If {@link initialize()} wasn't called - */ - public function enterNode(Node $node) - { - if (null === $this->currentContext) { - throw new RuntimeException( - 'No context was started yet. Call startContext() before '. - 'enterNode().' - ); - } - - $this->currentContext->pushNode($node); - } - - /** - * {@inheritdoc} - * - * @throws RuntimeException If {@link initialize()} wasn't called - */ - public function leaveNode(Node $node) - { - if (null === $this->currentContext) { - throw new RuntimeException( - 'No context was started yet. Call startContext() before '. - 'leaveNode().' - ); - } - - $this->currentContext->popNode(); - } - - /** - * Creates a new context. - * - * Can be overridden by subclasses. - * - * @param mixed $root The root value of the - * validated object graph - * @param ValidatorInterface $validator The validator - * @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 - * - * @return ExecutionContextInterface The created context - */ - protected function createContext($root, ValidatorInterface $validator, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain) - { - return new ExecutionContext($root, $validator, $groupManager, $translator, $translationDomain); - } -} diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextManagerInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextManagerInterface.php deleted file mode 100644 index a2dd04d7dc..0000000000 --- a/src/Symfony/Component/Validator/Context/ExecutionContextManagerInterface.php +++ /dev/null @@ -1,86 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Validator\Context; - -/** - * Manages the creation and deletion of {@link ExecutionContextInterface} - * instances. - * - * Start a new context with {@link startContext()}. You can retrieve the context - * with {@link getCurrentContext()} and stop it again with {@link stopContext()}. - * - * $contextManager->startContext(); - * $context = $contextManager->getCurrentContext(); - * $contextManager->stopContext(); - * - * You can also start several nested contexts. The {@link getCurrentContext()} - * method will always return the most recently started context. - * - * // Start context 1 - * $contextManager->startContext(); - * - * // Start context 2 - * $contextManager->startContext(); - * - * // Returns context 2 - * $context = $contextManager->getCurrentContext(); - * - * // Stop context 2 - * $contextManager->stopContext(); - * - * // Returns context 1 - * $context = $contextManager->getCurrentContext(); - * - * See also {@link ExecutionContextInterface} for more information. - * - * @since 2.5 - * @author Bernhard Schussek - * - * @see ExecutionContextInterface - */ -interface ExecutionContextManagerInterface -{ - /** - * Starts a new context. - * - * The newly started context is returned. You can subsequently access the - * context with {@link getCurrentContext()}. - * - * @param mixed $root The root value of the object graph in the new context - * - * @return ExecutionContextInterface The started context - */ - public function startContext($root); - - /** - * Stops the current context. - * - * If multiple contexts have been started, the most recently started context - * is stopped. The stopped context is returned from this method. - * - * After calling this method, {@link getCurrentContext()} will return the - * context that was started before the stopped context. - * - * @return ExecutionContextInterface The stopped context - */ - public function stopContext(); - - /** - * Returns the current context. - * - * If multiple contexts have been started, the current context refers to the - * most recently started context. - * - * @return ExecutionContextInterface The current context - */ - public function getCurrentContext(); -} diff --git a/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php b/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php index 685f06931b..2d85ab7905 100644 --- a/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php @@ -39,8 +39,11 @@ class LegacyExecutionContext extends ExecutionContext implements LegacyExecution * it does not, an {@link InvalidArgumentException} is thrown. * * @see ExecutionContext::__construct() + * + * @internal Called by {@link LegacyExecutionContextFactory}. Should not be used + * in user code. */ - public function __construct($root, ValidatorInterface $validator, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null) + public function __construct(ValidatorInterface $validator, $root, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain = null) { if (!$validator instanceof LegacyValidatorInterface) { throw new InvalidArgumentException( @@ -49,7 +52,13 @@ class LegacyExecutionContext extends ExecutionContext implements LegacyExecution ); } - parent::__construct($root, $validator, $groupManager, $translator, $translationDomain); + parent::__construct( + $validator, + $root, + $groupManager, + $translator, + $translationDomain + ); } /** diff --git a/src/Symfony/Component/Validator/Context/LegacyExecutionContextFactory.php b/src/Symfony/Component/Validator/Context/LegacyExecutionContextFactory.php new file mode 100644 index 0000000000..181b4f47fd --- /dev/null +++ b/src/Symfony/Component/Validator/Context/LegacyExecutionContextFactory.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\Context; + +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Validator\Group\GroupManagerInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; + +/** + * Creates new {@link LegacyExecutionContext} instances. + * + * @since 2.5 + * @author Bernhard Schussek + * + * @deprecated Implemented for backwards compatibility with Symfony < 2.5. To be + * removed in 3.0. + */ +class LegacyExecutionContextFactory implements ExecutionContextFactoryInterface +{ + /** + * @var GroupManagerInterface + */ + private $groupManager; + + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var string|null + */ + private $translationDomain; + + /** + * 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) + { + $this->groupManager = $groupManager; + $this->translator = $translator; + $this->translationDomain = $translationDomain; + } + + /** + * {@inheritdoc} + */ + public function createContext(ValidatorInterface $validator, $root) + { + return new LegacyExecutionContext( + $validator, + $root, + $this->groupManager, + $this->translator, + $this->translationDomain + ); + } +} diff --git a/src/Symfony/Component/Validator/Context/LegacyExecutionContextManager.php b/src/Symfony/Component/Validator/Context/LegacyExecutionContextManager.php deleted file mode 100644 index 361445bbfc..0000000000 --- a/src/Symfony/Component/Validator/Context/LegacyExecutionContextManager.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Validator\Context; - -use Symfony\Component\Translation\TranslatorInterface; -use Symfony\Component\Validator\Group\GroupManagerInterface; -use Symfony\Component\Validator\Validator\ValidatorInterface; - -/** - * A context manager that creates contexts compatible to the API < Symfony 2.5. - * - * @since 2.5 - * @author Bernhard Schussek - * - * @see ExecutionContextManagerInterface - * @see \Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface - */ -class LegacyExecutionContextManager extends ExecutionContextManager -{ - /** - * {@inheritdoc} - */ - protected function createContext($root, ValidatorInterface $validator, GroupManagerInterface $groupManager, TranslatorInterface $translator, $translationDomain) - { - return new LegacyExecutionContext($root, $validator, $groupManager, $translator, $translationDomain); - } -} diff --git a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php index eacfd85c2c..9d6f4d6394 100644 --- a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php +++ b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\NodeTraverser; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Exception\ConstraintDefinitionException; use Symfony\Component\Validator\Exception\NoSuchMetadataException; use Symfony\Component\Validator\Mapping\CascadingStrategy; @@ -59,7 +60,7 @@ class NodeTraverser implements NodeTraverserInterface /** * {@inheritdoc} */ - public function traverse(array $nodes) + public function traverse(array $nodes, ExecutionContextInterface $context) { $isTopLevelCall = !$this->traversalStarted; @@ -68,39 +69,34 @@ class NodeTraverser implements NodeTraverserInterface foreach ($this->visitors as $visitor) { /** @var NodeVisitorInterface $visitor */ - $visitor->beforeTraversal($nodes); + $visitor->beforeTraversal($nodes, $context); } } foreach ($nodes as $node) { if ($node instanceof ClassNode) { - $this->traverseClassNode($node); + $this->traverseClassNode($node, $context); } else { - $this->traverseNode($node); + $this->traverseNode($node, $context); } } if ($isTopLevelCall) { foreach ($this->visitors as $visitor) { /** @var NodeVisitorInterface $visitor */ - $visitor->afterTraversal($nodes); + $visitor->afterTraversal($nodes, $context); } $this->traversalStarted = false; } } - /** - * @param Node $node - * - * @return Boolean - */ - private function enterNode(Node $node) + private function enterNode(Node $node, ExecutionContextInterface $context) { $continueTraversal = true; foreach ($this->visitors as $visitor) { - if (false === $visitor->enterNode($node)) { + if (false === $visitor->enterNode($node, $context)) { $continueTraversal = false; // Continue, so that the enterNode() method of all visitors @@ -111,19 +107,16 @@ class NodeTraverser implements NodeTraverserInterface return $continueTraversal; } - /** - * @param Node $node - */ - private function leaveNode(Node $node) + private function leaveNode(Node $node, ExecutionContextInterface $context) { foreach ($this->visitors as $visitor) { - $visitor->leaveNode($node); + $visitor->leaveNode($node, $context); } } - private function traverseNode(Node $node) + private function traverseNode(Node $node, ExecutionContextInterface $context) { - $continue = $this->enterNode($node); + $continue = $this->enterNode($node, $context); // Visitors have two possibilities to influence the traversal: // @@ -133,13 +126,13 @@ class NodeTraverser implements NodeTraverserInterface // that group will be skipped in the subtree of that node. if (false === $continue) { - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } if (null === $node->value) { - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } @@ -151,7 +144,7 @@ class NodeTraverser implements NodeTraverserInterface : $node->groups; if (0 === count($cascadedGroups)) { - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } @@ -166,11 +159,12 @@ class NodeTraverser implements NodeTraverserInterface $this->cascadeEachObjectIn( $node->value, $node->propertyPath, - $node->cascadedGroups, - $traversalStrategy + $cascadedGroups, + $traversalStrategy, + $context ); - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } @@ -182,25 +176,26 @@ class NodeTraverser implements NodeTraverserInterface $this->cascadeObject( $node->value, $node->propertyPath, - $node->cascadedGroups, - $traversalStrategy + $cascadedGroups, + $traversalStrategy, + $context ); - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } // Traverse only if the TRAVERSE bit is set if (!($traversalStrategy & TraversalStrategy::TRAVERSE)) { - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } if (!$node->value instanceof \Traversable) { if ($traversalStrategy & TraversalStrategy::IGNORE_NON_TRAVERSABLE) { - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } @@ -215,16 +210,17 @@ class NodeTraverser implements NodeTraverserInterface $this->cascadeEachObjectIn( $node->value, $node->propertyPath, - $node->groups, - $traversalStrategy + $cascadedGroups, + $traversalStrategy, + $context ); - $this->leaveNode($node); + $this->leaveNode($node, $context); } - private function traverseClassNode(ClassNode $node, $traversalStrategy = TraversalStrategy::IMPLICIT) + private function traverseClassNode(ClassNode $node, ExecutionContextInterface $context, $traversalStrategy = TraversalStrategy::IMPLICIT) { - $continue = $this->enterNode($node); + $continue = $this->enterNode($node, $context); // Visitors have two possibilities to influence the traversal: // @@ -234,28 +230,30 @@ class NodeTraverser implements NodeTraverserInterface // that group will be skipped in the subtree of that node. if (false === $continue) { - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } if (0 === count($node->groups)) { - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } foreach ($node->metadata->getConstrainedProperties() as $propertyName) { foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) { - $this->traverseNode(new PropertyNode( - $propertyMetadata->getPropertyValue($node->value), - $propertyMetadata, - $node->propertyPath - ? $node->propertyPath.'.'.$propertyName - : $propertyName, - $node->groups, - $node->cascadedGroups - )); + $propertyNode = new PropertyNode( + $propertyMetadata->getPropertyValue($node->value), + $propertyMetadata, + $node->propertyPath + ? $node->propertyPath.'.'.$propertyName + : $propertyName, + $node->groups, + $node->cascadedGroups + ); + + $this->traverseNode($propertyNode, $context); } } @@ -267,14 +265,14 @@ class NodeTraverser implements NodeTraverserInterface // Traverse only if the TRAVERSE bit is set if (!($traversalStrategy & TraversalStrategy::TRAVERSE)) { - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } if (!$node->value instanceof \Traversable) { if ($traversalStrategy & TraversalStrategy::IGNORE_NON_TRAVERSABLE) { - $this->leaveNode($node); + $this->leaveNode($node, $context); return; } @@ -290,13 +288,14 @@ class NodeTraverser implements NodeTraverserInterface $node->value, $node->propertyPath, $node->groups, - $traversalStrategy + $traversalStrategy, + $context ); - $this->leaveNode($node); + $this->leaveNode($node, $context); } - private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy) + private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context) { try { $classMetadata = $this->metadataFactory->getMetadataFor($object); @@ -312,7 +311,7 @@ class NodeTraverser implements NodeTraverserInterface $groups ); - $this->traverseClassNode($classNode, $traversalStrategy); + $this->traverseClassNode($classNode, $context, $traversalStrategy); } catch (NoSuchMetadataException $e) { // Rethrow if the TRAVERSE bit is not set if (!($traversalStrategy & TraversalStrategy::TRAVERSE)) { @@ -329,12 +328,13 @@ class NodeTraverser implements NodeTraverserInterface $object, $propertyPath, $groups, - $traversalStrategy + $traversalStrategy, + $context ); } } - private function cascadeEachObjectIn($collection, $propertyPath, array $groups, $traversalStrategy) + private function cascadeEachObjectIn($collection, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context) { if ($traversalStrategy & TraversalStrategy::RECURSIVE) { // Try to traverse nested objects, but ignore if they do not @@ -356,7 +356,8 @@ class NodeTraverser implements NodeTraverserInterface $value, $propertyPath.'['.$key.']', $groups, - $traversalStrategy + $traversalStrategy, + $context ); continue; @@ -369,7 +370,8 @@ class NodeTraverser implements NodeTraverserInterface $value, $propertyPath.'['.$key.']', $groups, - $traversalStrategy + $traversalStrategy, + $context ); } } diff --git a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php index d9ce42f025..6084403d9b 100644 --- a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php +++ b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Validator\NodeTraverser; -use Symfony\Component\Validator\Node\Node; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface; /** @@ -24,8 +24,5 @@ interface NodeTraverserInterface public function removeVisitor(NodeVisitorInterface $visitor); - /** - * @param Node[] $nodes - */ - public function traverse(array $nodes); + public function traverse(array $nodes, ExecutionContextInterface $context); } diff --git a/src/Symfony/Component/Validator/NodeVisitor/AbstractVisitor.php b/src/Symfony/Component/Validator/NodeVisitor/AbstractVisitor.php index 31b49250eb..47631556f0 100644 --- a/src/Symfony/Component/Validator/NodeVisitor/AbstractVisitor.php +++ b/src/Symfony/Component/Validator/NodeVisitor/AbstractVisitor.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\NodeVisitor; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Node\Node; /** @@ -19,19 +20,19 @@ use Symfony\Component\Validator\Node\Node; */ abstract class AbstractVisitor implements NodeVisitorInterface { - public function beforeTraversal(array $nodes) + public function beforeTraversal(array $nodes, ExecutionContextInterface $context) { } - public function afterTraversal(array $nodes) + public function afterTraversal(array $nodes, ExecutionContextInterface $context) { } - public function enterNode(Node $node) + public function enterNode(Node $node, ExecutionContextInterface $context) { } - public function leaveNode(Node $node) + public function leaveNode(Node $node, ExecutionContextInterface $context) { } } diff --git a/src/Symfony/Component/Validator/NodeVisitor/ContextRefresher.php b/src/Symfony/Component/Validator/NodeVisitor/ContextRefresher.php new file mode 100644 index 0000000000..e647e7c1ad --- /dev/null +++ b/src/Symfony/Component/Validator/NodeVisitor/ContextRefresher.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Validator\NodeVisitor; + +use Symfony\Component\Validator\Context\ExecutionContext; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Validator\Exception\RuntimeException; +use Symfony\Component\Validator\Node\Node; + +/** + * Updates the current context with the current node of the validation + * traversal. + * + * @since 2.5 + * @author Bernhard Schussek + */ +class ContextRefresher extends AbstractVisitor +{ + public function enterNode(Node $node, ExecutionContextInterface $context) + { + if (!$context instanceof ExecutionContext) { + throw new RuntimeException(sprintf( + 'The ContextRefresher only supports instances of class '. + '"Symfony\Component\Validator\Context\ExecutionContext". '. + 'An instance of class "%s" was given.', + get_class($context) + )); + } + + $context->pushNode($node); + } + + /** + * {@inheritdoc} + * + * @throws RuntimeException If {@link initialize()} wasn't called + */ + public function leaveNode(Node $node, ExecutionContextInterface $context) + { + if (!$context instanceof ExecutionContext) { + throw new RuntimeException(sprintf( + 'The ContextRefresher only supports instances of class '. + '"Symfony\Component\Validator\Context\ExecutionContext". '. + 'An instance of class "%s" was given.', + get_class($context) + )); + } + + $context->popNode(); + } +} diff --git a/src/Symfony/Component/Validator/NodeVisitor/GroupSequenceResolver.php b/src/Symfony/Component/Validator/NodeVisitor/GroupSequenceResolver.php index 868d6fc428..b5887ba4b0 100644 --- a/src/Symfony/Component/Validator/NodeVisitor/GroupSequenceResolver.php +++ b/src/Symfony/Component/Validator/NodeVisitor/GroupSequenceResolver.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Validator\NodeVisitor; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\GroupSequence; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Node\ClassNode; use Symfony\Component\Validator\Node\Node; @@ -22,7 +23,7 @@ use Symfony\Component\Validator\Node\Node; */ class GroupSequenceResolver extends AbstractVisitor { - public function enterNode(Node $node) + public function enterNode(Node $node, ExecutionContextInterface $context) { if (!$node instanceof ClassNode) { return; diff --git a/src/Symfony/Component/Validator/NodeVisitor/NodeValidator.php b/src/Symfony/Component/Validator/NodeVisitor/NodeValidator.php index 6ee8b7f826..e410cb259e 100644 --- a/src/Symfony/Component/Validator/NodeVisitor/NodeValidator.php +++ b/src/Symfony/Component/Validator/NodeVisitor/NodeValidator.php @@ -13,7 +13,7 @@ namespace Symfony\Component\Validator\NodeVisitor; use Symfony\Component\Validator\Constraints\GroupSequence; use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; -use Symfony\Component\Validator\Context\ExecutionContextManagerInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Group\GroupManagerInterface; use Symfony\Component\Validator\Node\ClassNode; use Symfony\Component\Validator\Node\Node; @@ -35,11 +35,6 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface */ private $validatorFactory; - /** - * @var ExecutionContextManagerInterface - */ - private $contextManager; - /** * @var NodeTraverserInterface */ @@ -56,19 +51,14 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface $this->objectHashStack = new \SplStack(); } - public function initialize(ExecutionContextManagerInterface $contextManager) - { - $this->contextManager = $contextManager; - } - - public function afterTraversal(array $nodes) + public function afterTraversal(array $nodes, ExecutionContextInterface $context) { $this->validatedObjects = array(); $this->validatedConstraints = array(); $this->objectHashStack = new \SplStack(); } - public function enterNode(Node $node) + public function enterNode(Node $node, ExecutionContextInterface $context) { if ($node instanceof ClassNode) { $objectHash = spl_object_hash($node->value); @@ -105,7 +95,7 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface // Validate normal group if (!$group instanceof GroupSequence) { - $this->validateNodeForGroup($objectHash, $node, $group); + $this->validateNodeForGroup($objectHash, $node, $group, $context); continue; } @@ -114,7 +104,7 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface unset($node->groups[$key]); // Traverse group sequence until a violation is generated - $this->traverseGroupSequence($node, $group); + $this->traverseGroupSequence($node, $group, $context); // Optimization: If the groups only contain the group sequence, // we can skip the traversal for the properties of the object @@ -126,7 +116,7 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface return true; } - public function leaveNode(Node $node) + public function leaveNode(Node $node, ExecutionContextInterface $context) { if ($node instanceof ClassNode) { $this->objectHashStack->pop(); @@ -138,9 +128,8 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface return $this->currentGroup; } - private function traverseGroupSequence(Node $node, GroupSequence $groupSequence) + private function traverseGroupSequence(Node $node, GroupSequence $groupSequence, ExecutionContextInterface $context) { - $context = $this->contextManager->getCurrentContext(); $violationCount = count($context->getViolations()); foreach ($groupSequence->groups as $groupInSequence) { @@ -151,7 +140,7 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface $node->cascadedGroups = array($groupSequence->cascadedGroup); } - $this->nodeTraverser->traverse(array($node)); + $this->nodeTraverser->traverse(array($node), $context); // Abort sequence validation if a violation was generated if (count($context->getViolations()) > $violationCount) { @@ -160,14 +149,7 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface } } - /** - * @param $objectHash - * @param Node $node - * @param $group - * - * @throws \Exception - */ - private function validateNodeForGroup($objectHash, Node $node, $group) + private function validateNodeForGroup($objectHash, Node $node, $group, ExecutionContextInterface $context) { try { $this->currentGroup = $group; @@ -187,7 +169,7 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface } $validator = $this->validatorFactory->getInstance($constraint); - $validator->initialize($this->contextManager->getCurrentContext()); + $validator->initialize($context); $validator->validate($node->value, $constraint); } diff --git a/src/Symfony/Component/Validator/NodeVisitor/NodeVisitorInterface.php b/src/Symfony/Component/Validator/NodeVisitor/NodeVisitorInterface.php index a7542d515a..6736cc23ba 100644 --- a/src/Symfony/Component/Validator/NodeVisitor/NodeVisitorInterface.php +++ b/src/Symfony/Component/Validator/NodeVisitor/NodeVisitorInterface.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\NodeVisitor; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Node\Node; /** @@ -19,11 +20,11 @@ use Symfony\Component\Validator\Node\Node; */ interface NodeVisitorInterface { - public function beforeTraversal(array $nodes); + public function beforeTraversal(array $nodes, ExecutionContextInterface $context); - public function afterTraversal(array $nodes); + public function afterTraversal(array $nodes, ExecutionContextInterface $context); - public function enterNode(Node $node); + public function enterNode(Node $node, ExecutionContextInterface $context); - public function leaveNode(Node $node); + public function leaveNode(Node $node, ExecutionContextInterface $context); } diff --git a/src/Symfony/Component/Validator/NodeVisitor/ObjectInitializer.php b/src/Symfony/Component/Validator/NodeVisitor/ObjectInitializer.php index bd000366c5..8d44d39ee0 100644 --- a/src/Symfony/Component/Validator/NodeVisitor/ObjectInitializer.php +++ b/src/Symfony/Component/Validator/NodeVisitor/ObjectInitializer.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Validator\NodeVisitor; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Node\ClassNode; use Symfony\Component\Validator\Node\Node; use Symfony\Component\Validator\ObjectInitializerInterface; @@ -42,7 +43,7 @@ class ObjectInitializer extends AbstractVisitor $this->initializers = $initializers; } - public function enterNode(Node $node) + public function enterNode(Node $node, ExecutionContextInterface $context) { if (!$node instanceof ClassNode) { return; diff --git a/src/Symfony/Component/Validator/Tests/Context/ExecutionContextManagerTest.php b/src/Symfony/Component/Validator/Tests/Context/ExecutionContextManagerTest.php deleted file mode 100644 index 548a11170d..0000000000 --- a/src/Symfony/Component/Validator/Tests/Context/ExecutionContextManagerTest.php +++ /dev/null @@ -1,95 +0,0 @@ - - * - * 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\ExecutionContextManager; - -/** - * @since 2.5 - * @author Bernhard Schussek - */ -class ExecutionContextManagerTest extends \PHPUnit_Framework_TestCase -{ - 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 ExecutionContextManager - */ - private $contextManager; - - protected function setUp() - { - $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->contextManager = new ExecutionContextManager( - $this->groupManager, - $this->translator, - self::TRANSLATION_DOMAIN - ); - } - - /** - * @expectedException \Symfony\Component\Validator\Exception\RuntimeException - */ - public function testInitializeMustBeCalledBeforeStartContext() - { - $this->contextManager->startContext('root'); - } - - /** - * @expectedException \Symfony\Component\Validator\Exception\RuntimeException - */ - public function testCannotStopContextIfNoneWasStarted() - { - $this->contextManager->stopContext(); - } - - /** - * @expectedException \Symfony\Component\Validator\Exception\RuntimeException - */ - public function testCannotEnterNodeWithoutActiveContext() - { - $node = $this->getMockBuilder('Symfony\Component\Validator\Node\Node') - ->disableOriginalConstructor() - ->getMock(); - - $this->contextManager->enterNode($node); - } - - /** - * @expectedException \Symfony\Component\Validator\Exception\RuntimeException - */ - public function testCannotLeaveNodeWithoutActiveContext() - { - $node = $this->getMockBuilder('Symfony\Component\Validator\Node\Node') - ->disableOriginalConstructor() - ->getMock(); - - $this->contextManager->leaveNode($node); - } -} diff --git a/src/Symfony/Component/Validator/Tests/Context/ExecutionContextTest.php b/src/Symfony/Component/Validator/Tests/Context/ExecutionContextTest.php index 1133f413a7..8019aecea1 100644 --- a/src/Symfony/Component/Validator/Tests/Context/ExecutionContextTest.php +++ b/src/Symfony/Component/Validator/Tests/Context/ExecutionContextTest.php @@ -51,11 +51,7 @@ class ExecutionContextTest extends \PHPUnit_Framework_TestCase $this->translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface'); $this->context = new ExecutionContext( - self::ROOT, - $this->validator, - $this->groupManager, - $this->translator, - self::TRANSLATION_DOMAIN + $this->validator, self::ROOT, $this->groupManager, $this->translator, self::TRANSLATION_DOMAIN ); } diff --git a/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php index 48d8f07141..ad529f3750 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php @@ -87,7 +87,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest 'groups' => array('Group 1', 'Group 2'), ))); - $violations = $this->validateObject($entity, array('Group 1', 'Group 2')); + $violations = $this->validator->validateObject($entity, array('Group 1', 'Group 2')); /** @var ConstraintViolationInterface[] $violations */ $this->assertCount(1, $violations); @@ -118,7 +118,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest ))); $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3')); - $violations = $this->validateObject($entity, $sequence); + $violations = $this->validator->validateObject($entity, $sequence); /** @var ConstraintViolationInterface[] $violations */ $this->assertCount(1, $violations); @@ -148,7 +148,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest ))); $sequence = new GroupSequence(array('Group 1', 'Entity')); - $violations = $this->validateObject($entity, $sequence); + $violations = $this->validator->validateObject($entity, $sequence); /** @var ConstraintViolationInterface[] $violations */ $this->assertCount(1, $violations); @@ -207,7 +207,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest 'groups' => 'Group', ))); - $violations = $this->validateObject($entity, 'Group'); + $violations = $this->validator->validateObject($entity, 'Group'); /** @var ConstraintViolationInterface[] $violations */ $this->assertCount(1, $violations); @@ -251,7 +251,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest 'groups' => 'Group', ))); - $violations = $this->validateObject($entity, 'Group'); + $violations = $this->validator->validateObject($entity, 'Group'); /** @var ConstraintViolationInterface[] $violations */ $this->assertCount(1, $violations); @@ -302,7 +302,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest 'groups' => 'Group', ))); - $violations = $this->validateObject($entity, 'Group'); + $violations = $this->validator->validateObject($entity, 'Group'); /** @var ConstraintViolationInterface[] $violations */ $this->assertCount(1, $violations); @@ -361,7 +361,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest { $entity = new Entity(); - $this->validate($entity, new Traverse()); + $this->validator->validate($entity, new Traverse()); } /** @@ -373,7 +373,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest $this->metadata->addConstraint(new Traverse()); - $this->validateObject($entity); + $this->validator->validateObject($entity); } public function testAddCustomizedViolation() @@ -391,7 +391,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest $this->metadata->addConstraint(new Callback($callback)); - $violations = $this->validateObject($entity); + $violations = $this->validator->validateObject($entity); /** @var ConstraintViolationInterface[] $violations */ $this->assertCount(1, $violations); diff --git a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php index e3715731ce..1cecafdedf 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php @@ -13,8 +13,9 @@ namespace Symfony\Component\Validator\Tests\Validator; use Symfony\Component\Validator\DefaultTranslator; use Symfony\Component\Validator\ConstraintValidatorFactory; -use Symfony\Component\Validator\Context\LegacyExecutionContextManager; +use Symfony\Component\Validator\Context\LegacyExecutionContextFactory; use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\NodeVisitor\ContextRefresher; use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolver; use Symfony\Component\Validator\NodeVisitor\NodeValidator; use Symfony\Component\Validator\NodeTraverser\NodeTraverser; @@ -26,20 +27,13 @@ class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest { $nodeTraverser = new NodeTraverser($metadataFactory); $nodeValidator = new NodeValidator($nodeTraverser, new ConstraintValidatorFactory()); - $contextManager = new LegacyExecutionContextManager($nodeValidator, new DefaultTranslator()); - $validator = new LegacyValidator($nodeTraverser, $metadataFactory, $contextManager); + $contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator()); + $validator = new LegacyValidator($contextFactory, $nodeTraverser, $metadataFactory); $groupSequenceResolver = new GroupSequenceResolver(); - - // The context manager needs the validator for passing it to created - // contexts - $contextManager->initialize($validator); - - // The node validator needs the context manager for passing the current - // context to the constraint validators - $nodeValidator->initialize($contextManager); + $contextRefresher = new ContextRefresher(); $nodeTraverser->addVisitor($groupSequenceResolver); - $nodeTraverser->addVisitor($contextManager); + $nodeTraverser->addVisitor($contextRefresher); $nodeTraverser->addVisitor($nodeValidator); return $validator; diff --git a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php index c106d765c1..94900e4497 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php @@ -13,8 +13,9 @@ namespace Symfony\Component\Validator\Tests\Validator; use Symfony\Component\Validator\DefaultTranslator; use Symfony\Component\Validator\ConstraintValidatorFactory; -use Symfony\Component\Validator\Context\LegacyExecutionContextManager; +use Symfony\Component\Validator\Context\LegacyExecutionContextFactory; use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\NodeVisitor\ContextRefresher; use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolver; use Symfony\Component\Validator\NodeVisitor\NodeValidator; use Symfony\Component\Validator\NodeTraverser\NodeTraverser; @@ -26,20 +27,13 @@ class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest { $nodeTraverser = new NodeTraverser($metadataFactory); $nodeValidator = new NodeValidator($nodeTraverser, new ConstraintValidatorFactory()); - $contextManager = new LegacyExecutionContextManager($nodeValidator, new DefaultTranslator()); - $validator = new LegacyValidator($nodeTraverser, $metadataFactory, $contextManager); + $contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator()); + $validator = new LegacyValidator($contextFactory, $nodeTraverser, $metadataFactory); $groupSequenceResolver = new GroupSequenceResolver(); - - // The context manager needs the validator for passing it to created - // contexts - $contextManager->initialize($validator); - - // The node validator needs the context manager for passing the current - // context to the constraint validators - $nodeValidator->initialize($contextManager); + $contextRefresher = new ContextRefresher(); $nodeTraverser->addVisitor($groupSequenceResolver); - $nodeTraverser->addVisitor($contextManager); + $nodeTraverser->addVisitor($contextRefresher); $nodeTraverser->addVisitor($nodeValidator); return $validator; diff --git a/src/Symfony/Component/Validator/Tests/Validator/Validator2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/Validator2Dot5ApiTest.php index 2bf4693dde..4d58f0a13e 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/Validator2Dot5ApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/Validator2Dot5ApiTest.php @@ -13,8 +13,9 @@ namespace Symfony\Component\Validator\Tests\Validator; use Symfony\Component\Validator\DefaultTranslator; use Symfony\Component\Validator\ConstraintValidatorFactory; -use Symfony\Component\Validator\Context\ExecutionContextManager; +use Symfony\Component\Validator\Context\ExecutionContextFactory; use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\NodeVisitor\ContextRefresher; use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolver; use Symfony\Component\Validator\NodeVisitor\NodeValidator; use Symfony\Component\Validator\NodeTraverser\NodeTraverser; @@ -26,20 +27,13 @@ class Validator2Dot5ApiTest extends Abstract2Dot5ApiTest { $nodeTraverser = new NodeTraverser($metadataFactory); $nodeValidator = new NodeValidator($nodeTraverser, new ConstraintValidatorFactory()); - $contextManager = new ExecutionContextManager($nodeValidator, new DefaultTranslator()); - $validator = new Validator($nodeTraverser, $metadataFactory, $contextManager); + $contextFactory = new ExecutionContextFactory($nodeValidator, new DefaultTranslator()); + $validator = new Validator($contextFactory, $nodeTraverser, $metadataFactory); $groupSequenceResolver = new GroupSequenceResolver(); - - // The context manager needs the validator for passing it to created - // contexts - $contextManager->initialize($validator); - - // The node validator needs the context manager for passing the current - // context to the constraint validators - $nodeValidator->initialize($contextManager); + $contextRefresher = new ContextRefresher(); $nodeTraverser->addVisitor($groupSequenceResolver); - $nodeTraverser->addVisitor($contextManager); + $nodeTraverser->addVisitor($contextRefresher); $nodeTraverser->addVisitor($nodeValidator); return $validator; diff --git a/src/Symfony/Component/Validator/Validator/AbstractValidator.php b/src/Symfony/Component/Validator/Validator/AbstractValidator.php deleted file mode 100644 index 8cd2767bd0..0000000000 --- a/src/Symfony/Component/Validator/Validator/AbstractValidator.php +++ /dev/null @@ -1,188 +0,0 @@ - - * - * 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\Context\ExecutionContextInterface; -use Symfony\Component\Validator\Exception\ValidatorException; -use Symfony\Component\Validator\Mapping\ClassMetadataInterface; -use Symfony\Component\Validator\Mapping\GenericMetadata; -use Symfony\Component\Validator\MetadataFactoryInterface; -use Symfony\Component\Validator\Node\ClassNode; -use Symfony\Component\Validator\Node\PropertyNode; -use Symfony\Component\Validator\Node\GenericNode; -use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface; -use Symfony\Component\Validator\Util\PropertyPath; - -/** - * @since %%NextVersion%% - * @author Bernhard Schussek - */ -abstract class AbstractValidator implements ValidatorInterface -{ - /** - * @var NodeTraverserInterface - */ - protected $nodeTraverser; - - /** - * @var MetadataFactoryInterface - */ - protected $metadataFactory; - - /** - * @var string - */ - protected $defaultPropertyPath = ''; - - protected $defaultGroups = array(Constraint::DEFAULT_GROUP); - - public function __construct(NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory) - { - $this->nodeTraverser = $nodeTraverser; - $this->metadataFactory = $metadataFactory; - } - - /** - * @param ExecutionContextInterface $context - * - * @return ContextualValidatorInterface - */ - public function inContext(ExecutionContextInterface $context) - { - return new ContextualValidator($this->nodeTraverser, $this->metadataFactory, $context); - } - - public function getMetadataFor($object) - { - return $this->metadataFactory->getMetadataFor($object); - } - - public function hasMetadataFor($object) - { - return $this->metadataFactory->hasMetadataFor($object); - } - - protected function traverse($value, $constraints, $groups = null) - { - if (!is_array($constraints)) { - $constraints = array($constraints); - } - - $metadata = new GenericMetadata(); - $metadata->addConstraints($constraints); - $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; - - $this->nodeTraverser->traverse(array(new GenericNode( - $value, - $metadata, - $this->defaultPropertyPath, - $groups, - $groups - ))); - } - - protected function traverseObject($object, $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) - )); - } - - $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; - - $this->nodeTraverser->traverse(array(new ClassNode( - $object, - $classMetadata, - $this->defaultPropertyPath, - $groups, - $groups - ))); - } - - protected function traverseProperty($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; - $nodes = array(); - - foreach ($propertyMetadatas as $propertyMetadata) { - $propertyValue = $propertyMetadata->getPropertyValue($object); - - $nodes[] = new PropertyNode( - $propertyValue, - $propertyMetadata, - PropertyPath::append($this->defaultPropertyPath, $propertyName), - $groups, - $groups - ); - } - - $this->nodeTraverser->traverse($nodes); - } - - protected function traversePropertyValue($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; - $nodes = array(); - - foreach ($propertyMetadatas as $propertyMetadata) { - $nodes[] = new PropertyNode( - $value, - $propertyMetadata, - PropertyPath::append($this->defaultPropertyPath, $propertyName), - $groups, - $groups - ); - } - - $this->nodeTraverser->traverse($nodes); - } - - protected function normalizeGroups($groups) - { - if (is_array($groups)) { - return $groups; - } - - return array($groups); - } -} diff --git a/src/Symfony/Component/Validator/Validator/ContextualValidator.php b/src/Symfony/Component/Validator/Validator/ContextualValidator.php index b975ef4bde..cb9951bf77 100644 --- a/src/Symfony/Component/Validator/Validator/ContextualValidator.php +++ b/src/Symfony/Component/Validator/Validator/ContextualValidator.php @@ -13,30 +13,45 @@ namespace Symfony\Component\Validator\Validator; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints\Traverse; -use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; -use Symfony\Component\Validator\Context\ExecutionContextManagerInterface; +use Symfony\Component\Validator\Exception\ValidatorException; +use Symfony\Component\Validator\Mapping\ClassMetadataInterface; +use Symfony\Component\Validator\Mapping\GenericMetadata; use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\Node\ClassNode; +use Symfony\Component\Validator\Node\GenericNode; +use Symfony\Component\Validator\Node\PropertyNode; use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface; +use Symfony\Component\Validator\Util\PropertyPath; /** * @since %%NextVersion%% * @author Bernhard Schussek */ -class ContextualValidator extends AbstractValidator implements ContextualValidatorInterface +class ContextualValidator implements ContextualValidatorInterface { /** - * @var ExecutionContextManagerInterface + * @var ExecutionContextInterface */ private $context; - public function __construct(NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory, ExecutionContextInterface $context) - { - parent::__construct($nodeTraverser, $metadataFactory); + /** + * @var NodeTraverserInterface + */ + private $nodeTraverser; + /** + * @var MetadataFactoryInterface + */ + private $metadataFactory; + + public function __construct(ExecutionContextInterface $context, NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory) + { $this->context = $context; $this->defaultPropertyPath = $context->getPropertyPath(); - $this->defaultGroups = array($context->getGroup()); + $this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP); + $this->nodeTraverser = $nodeTraverser; + $this->metadataFactory = $metadataFactory; } public function atPath($subPath) @@ -46,40 +61,53 @@ class ContextualValidator extends AbstractValidator implements ContextualValidat return $this; } - /** - * Validates a value against a constraint or a list of constraints. - * - * @param mixed $value The value to validate. - * @param Constraint|Constraint[] $constraints The constraint(s) to validate against. - * @param array|null $groups The validation groups to validate. - * - * @return ConstraintViolationListInterface A list of constraint violations. If the - * list is empty, validation succeeded. - */ public function validate($value, $constraints, $groups = null) { - $this->traverse($value, $constraints, $groups); + if (!is_array($constraints)) { + $constraints = array($constraints); + } - return $this->context->getViolations(); + $metadata = new GenericMetadata(); + $metadata->addConstraints($constraints); + $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; + + $node = new GenericNode( + $value, + $metadata, + $this->defaultPropertyPath, + $groups + ); + + $this->nodeTraverser->traverse(array($node), $this->context); + + return $this; } - /** - * Validates a value. - * - * The accepted values depend on the {@link MetadataFactoryInterface} - * implementation. - * - * @param mixed $object The value to validate - * @param array|null $groups The validation groups to validate. - * - * @return ConstraintViolationListInterface A list of constraint violations. If the - * list is empty, validation succeeded. - */ public function validateObject($object, $groups = null) { - $this->traverseObject($object, $groups); + $classMetadata = $this->metadataFactory->getMetadataFor($object); - return $this->context->getViolations(); + 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) + )); + } + + $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups; + + $node = new ClassNode( + $object, + $classMetadata, + $this->defaultPropertyPath, + $groups + ); + + $this->nodeTraverser->traverse(array($node), $this->context); + + return $this; } public function validateCollection($collection, $groups = null, $deep = false) @@ -89,50 +117,85 @@ class ContextualValidator extends AbstractValidator implements ContextualValidat 'deep' => $deep, )); - $this->traverse($collection, $constraint, $groups); - - return $this->context->getViolations(); + return $this->validate($collection, $constraint, $groups); } - /** - * Validates a property of a value against its current value. - * - * The accepted values depend on the {@link MetadataFactoryInterface} - * implementation. - * - * @param mixed $object The value containing the property. - * @param string $propertyName The name of the property to validate. - * @param array|null $groups The validation groups to validate. - * - * @return ConstraintViolationListInterface A list of constraint violations. If the - * list is empty, validation succeeded. - */ public function validateProperty($object, $propertyName, $groups = null) { - $this->traverseProperty($object, $propertyName, $groups); + $classMetadata = $this->metadataFactory->getMetadataFor($object); - return $this->context->getViolations(); + 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; + $nodes = array(); + + foreach ($propertyMetadatas as $propertyMetadata) { + $propertyValue = $propertyMetadata->getPropertyValue($object); + + $nodes[] = new PropertyNode( + $propertyValue, + $propertyMetadata, + PropertyPath::append($this->defaultPropertyPath, $propertyName), + $groups + ); + } + + $this->nodeTraverser->traverse($nodes, $this->context); + + return $this; } - /** - * Validate a property of a value against a potential value. - * - * The accepted values depend on the {@link MetadataFactoryInterface} - * implementation. - * - * @param string $object The value containing the property. - * @param string $propertyName The name of the property to validate - * @param string $value The value to validate against the - * constraints of the property. - * @param array|null $groups The validation groups to validate. - * - * @return ConstraintViolationListInterface A list of constraint violations. If the - * list is empty, validation succeeded. - */ public function validatePropertyValue($object, $propertyName, $value, $groups = null) { - $this->traversePropertyValue($object, $propertyName, $value, $groups); + $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; + $nodes = array(); + + foreach ($propertyMetadatas as $propertyMetadata) { + $nodes[] = new PropertyNode( + $value, + $propertyMetadata, + PropertyPath::append($this->defaultPropertyPath, $propertyName), + $groups, + $groups + ); + } + + $this->nodeTraverser->traverse($nodes, $this->context); + + return $this; + } + + protected function normalizeGroups($groups) + { + if (is_array($groups)) { + return $groups; + } + + return array($groups); + } + + public function getViolations() + { return $this->context->getViolations(); } } diff --git a/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php index 61de8900f1..69a224cb19 100644 --- a/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php +++ b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php @@ -11,16 +11,80 @@ namespace Symfony\Component\Validator\Validator; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintViolationListInterface; + /** * @since %%NextVersion%% * @author Bernhard Schussek */ -interface ContextualValidatorInterface extends ValidatorInterface +interface ContextualValidatorInterface { /** - * @param $subPath + * @param string $subPath * - * @return ContextualValidatorInterface + * @return ContextualValidatorInterface This validator */ public function atPath($subPath); + + /** + * Validates a value against a constraint or a list of constraints. + * + * @param mixed $value The value to validate. + * @param Constraint|Constraint[] $constraints The constraint(s) to validate against. + * @param array|null $groups The validation groups to validate. + * + * @return ContextualValidatorInterface This validator + */ + public function validate($value, $constraints, $groups = null); + + /** + * Validates a value. + * + * The accepted values depend on the {@link MetadataFactoryInterface} + * implementation. + * + * @param mixed $object The value to validate + * @param array|null $groups The validation groups to validate. + * + * @return ContextualValidatorInterface This validator + */ + public function validateObject($object, $groups = null); + + public function validateCollection($collection, $groups = null, $deep = false); + + /** + * Validates a property of a value against its current value. + * + * The accepted values depend on the {@link MetadataFactoryInterface} + * implementation. + * + * @param mixed $object The value containing the property. + * @param string $propertyName The name of the property to validate. + * @param array|null $groups The validation groups to validate. + * + * @return ContextualValidatorInterface This validator + */ + public function validateProperty($object, $propertyName, $groups = null); + + /** + * Validate a property of a value against a potential value. + * + * The accepted values depend on the {@link MetadataFactoryInterface} + * implementation. + * + * @param string $object The value containing the property. + * @param string $propertyName The name of the property to validate + * @param string $value The value to validate against the + * constraints of the property. + * @param array|null $groups The validation groups to validate. + * + * @return ContextualValidatorInterface This validator + */ + public function validatePropertyValue($object, $propertyName, $value, $groups = null); + + /** + * @return ConstraintViolationListInterface + */ + public function getViolations(); } diff --git a/src/Symfony/Component/Validator/Validator/Validator.php b/src/Symfony/Component/Validator/Validator/Validator.php index 94857a3a1b..820b01a149 100644 --- a/src/Symfony/Component/Validator/Validator/Validator.php +++ b/src/Symfony/Component/Validator/Validator/Validator.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Validator\Validator; -use Symfony\Component\Validator\Constraints\Traverse; -use Symfony\Component\Validator\Context\ExecutionContextManagerInterface; +use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface; +use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface; use Symfony\Component\Validator\MetadataFactoryInterface; @@ -20,67 +20,102 @@ use Symfony\Component\Validator\MetadataFactoryInterface; * @since %%NextVersion%% * @author Bernhard Schussek */ -class Validator extends AbstractValidator +class Validator implements ValidatorInterface { /** - * @var ExecutionContextManagerInterface + * @var ExecutionContextFactoryInterface */ - protected $contextManager; + protected $contextFactory; - public function __construct(NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory, ExecutionContextManagerInterface $contextManager) + /** + * @var NodeTraverserInterface + */ + protected $nodeTraverser; + + /** + * @var MetadataFactoryInterface + */ + protected $metadataFactory; + + public function __construct(ExecutionContextFactoryInterface $contextFactory, NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory) { - parent::__construct($nodeTraverser, $metadataFactory); + $this->contextFactory = $contextFactory; + $this->nodeTraverser = $nodeTraverser; + $this->metadataFactory = $metadataFactory; + } - $this->contextManager = $contextManager; + /** + * {@inheritdoc} + */ + public function startContext($root = null) + { + return new ContextualValidator( + $this->contextFactory->createContext($this, $root), + $this->nodeTraverser, + $this->metadataFactory + ); + } + + /** + * {@inheritdoc} + */ + public function inContext(ExecutionContextInterface $context) + { + return new ContextualValidator( + $context, + $this->nodeTraverser, + $this->metadataFactory + ); + } + + /** + * {@inheritdoc} + */ + public function getMetadataFor($object) + { + return $this->metadataFactory->getMetadataFor($object); + } + + /** + * {@inheritdoc} + */ + public function hasMetadataFor($object) + { + return $this->metadataFactory->hasMetadataFor($object); } public function validate($value, $constraints, $groups = null) { - $this->contextManager->startContext($value); - - $this->traverse($value, $constraints, $groups); - - return $this->contextManager->stopContext()->getViolations(); + return $this->startContext($value) + ->validate($value, $constraints, $groups) + ->getViolations(); } public function validateObject($object, $groups = null) { - $this->contextManager->startContext($object); - - $this->traverseObject($object, $groups); - - return $this->contextManager->stopContext()->getViolations(); + return $this->startContext($object) + ->validateObject($object, $groups) + ->getViolations(); } public function validateCollection($collection, $groups = null, $deep = false) { - $this->contextManager->startContext($collection); - - $constraint = new Traverse(array( - 'traverse' => true, - 'deep' => $deep, - )); - - $this->traverse($collection, $constraint, $groups); - - return $this->contextManager->stopContext()->getViolations(); + return $this->startContext($collection) + ->validateCollection($collection, $groups, $deep) + ->getViolations(); } public function validateProperty($object, $propertyName, $groups = null) { - $this->contextManager->startContext($object); - - $this->traverseProperty($object, $propertyName, $groups); - - return $this->contextManager->stopContext()->getViolations(); + return $this->startContext($object) + ->validateProperty($object, $propertyName, $groups) + ->getViolations(); } public function validatePropertyValue($object, $propertyName, $value, $groups = null) { - $this->contextManager->startContext($object); - - $this->traversePropertyValue($object, $propertyName, $value, $groups); - - return $this->contextManager->stopContext()->getViolations(); + return $this->startContext($object) + ->validatePropertyValue($object, $propertyName, $value, $groups) + ->getViolations(); } } diff --git a/src/Symfony/Component/Validator/Validator/ValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php index 8cecaf3dc4..f69395284a 100644 --- a/src/Symfony/Component/Validator/Validator/ValidatorInterface.php +++ b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php @@ -81,6 +81,11 @@ interface ValidatorInterface */ public function validatePropertyValue($object, $propertyName, $value, $groups = null); + /** + * @return ContextualValidatorInterface + */ + public function startContext(); + /** * @param ExecutionContextInterface $context *