[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.
This commit is contained in:
Bernhard Schussek 2014-02-19 20:10:51 +01:00
parent 26eafa43f7
commit df41974f31
27 changed files with 668 additions and 905 deletions

View File

@ -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 <bschussek@gmail.com>
@ -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;

View File

@ -0,0 +1,72 @@
<?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\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 <bschussek@gmail.com>
*/
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
);
}
}

View File

@ -0,0 +1,37 @@
<?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\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 <bschussek@gmail.com>
*/
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);
}

View File

@ -1,215 +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\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 <bschussek@gmail.com>
*
* @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);
}
}

View File

@ -1,86 +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\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 <bschussek@gmail.com>
*
* @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();
}

View File

@ -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
);
}
/**

View File

@ -0,0 +1,75 @@
<?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\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 <bschussek@gmail.com>
*
* @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
);
}
}

View File

@ -1,36 +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\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 <bschussek@gmail.com>
*
* @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);
}
}

View File

@ -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
);
}
}

View File

@ -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);
}

View File

@ -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)
{
}
}

View File

@ -0,0 +1,60 @@
<?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\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 <bschussek@gmail.com>
*/
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();
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -1,95 +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\ExecutionContextManager;
/**
* @since 2.5
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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);
}
}

View File

@ -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
);
}

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,188 +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\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 <bschussek@gmail.com>
*/
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);
}
}

View File

@ -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 <bschussek@gmail.com>
*/
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();
}
}

View File

@ -11,16 +11,80 @@
namespace Symfony\Component\Validator\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
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();
}

View File

@ -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 <bschussek@gmail.com>
*/
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();
}
}

View File

@ -81,6 +81,11 @@ interface ValidatorInterface
*/
public function validatePropertyValue($object, $propertyName, $value, $groups = null);
/**
* @return ContextualValidatorInterface
*/
public function startContext();
/**
* @param ExecutionContextInterface $context
*