[Validator] Decoupled the new classes a bit

This commit is contained in:
Bernhard Schussek 2014-02-17 14:43:28 +01:00
parent a6ed4cae5d
commit a40189ccb7
9 changed files with 95 additions and 92 deletions

View File

@ -15,7 +15,6 @@ use Symfony\Component\Validator\ClassBasedInterface;
use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Group\GroupManagerInterface; use Symfony\Component\Validator\Group\GroupManagerInterface;
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Node\Node; use Symfony\Component\Validator\Node\Node;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
@ -39,11 +38,6 @@ class ExecutionContext implements ExecutionContextInterface
*/ */
private $nodeStack; private $nodeStack;
/**
* @var MetadataFactoryInterface
*/
private $metadataFactory;
/** /**
* @var ValidatorInterface * @var ValidatorInterface
*/ */
@ -54,9 +48,8 @@ class ExecutionContext implements ExecutionContextInterface
*/ */
private $groupManager; private $groupManager;
public function __construct(MetadataFactoryInterface $metadataFactory, ValidatorInterface $validator, GroupManagerInterface $groupManager) public function __construct(ValidatorInterface $validator, GroupManagerInterface $groupManager)
{ {
$this->metadataFactory = $metadataFactory;
$this->validator = $validator; $this->validator = $validator;
$this->groupManager = $groupManager; $this->groupManager = $groupManager;
$this->violations = new ConstraintViolationList(); $this->violations = new ConstraintViolationList();
@ -105,11 +98,6 @@ class ExecutionContext implements ExecutionContextInterface
} }
public function getMetadataFor($object)
{
}
public function getViolations() public function getViolations()
{ {
return $this->violations; return $this->violations;

View File

@ -88,8 +88,6 @@ interface ExecutionContextInterface
*/ */
public function getMetadata(); public function getMetadata();
public function getMetadataFor($object);
/** /**
* Returns the validation group that is currently being validated. * Returns the validation group that is currently being validated.
* *

View File

@ -12,7 +12,6 @@
namespace Symfony\Component\Validator\Context; namespace Symfony\Component\Validator\Context;
use Symfony\Component\Validator\Group\GroupManagerInterface; use Symfony\Component\Validator\Group\GroupManagerInterface;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Node\Node; use Symfony\Component\Validator\Node\Node;
use Symfony\Component\Validator\NodeTraverser\AbstractVisitor; use Symfony\Component\Validator\NodeTraverser\AbstractVisitor;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
@ -23,11 +22,6 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
*/ */
class ExecutionContextManager extends AbstractVisitor implements ExecutionContextManagerInterface class ExecutionContextManager extends AbstractVisitor implements ExecutionContextManagerInterface
{ {
/**
* @var MetadataFactoryInterface
*/
private $metadataFactory;
/** /**
* @var GroupManagerInterface * @var GroupManagerInterface
*/ */
@ -48,9 +42,8 @@ class ExecutionContextManager extends AbstractVisitor implements ExecutionContex
*/ */
private $contextStack; private $contextStack;
public function __construct(MetadataFactoryInterface $metadataFactory, GroupManagerInterface $groupManager) public function __construct(GroupManagerInterface $groupManager)
{ {
$this->metadataFactory = $metadataFactory;
$this->groupManager = $groupManager; $this->groupManager = $groupManager;
$this->reset(); $this->reset();
@ -67,7 +60,7 @@ class ExecutionContextManager extends AbstractVisitor implements ExecutionContex
$this->contextStack->push($this->currentContext); $this->contextStack->push($this->currentContext);
} }
$this->currentContext = new ExecutionContext($this->metadataFactory, $this->validator, $this->groupManager); $this->currentContext = new ExecutionContext($this->validator, $this->groupManager);
return $this->currentContext; return $this->currentContext;
} }

View File

@ -12,8 +12,10 @@
namespace Symfony\Component\Validator\NodeTraverser; namespace Symfony\Component\Validator\NodeTraverser;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Context\ExecutionContextManagerInterface;
use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Node\ClassNode;
use Symfony\Component\Validator\Node\Node;
use Symfony\Component\Validator\Node\PropertyNode;
/** /**
* @since %%NextVersion%% * @since %%NextVersion%%

View File

@ -25,8 +25,6 @@ interface NodeTraverserInterface
/** /**
* @param Node[] $nodes * @param Node[] $nodes
*
* @return mixed
*/ */
public function traverse(array $nodes); public function traverse(array $nodes);
} }

View File

@ -15,23 +15,27 @@ use Symfony\Component\Validator\Context\ExecutionContextManager;
use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Tests\AbstractValidatorTest; use Symfony\Component\Validator\Tests\AbstractValidatorTest;
use Symfony\Component\Validator\NodeTraverser\NodeTraverser; use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
use Symfony\Component\Validator\NodeTraverser\NodeVisitor\NodeValidator;
use Symfony\Component\Validator\DefaultTranslator; use Symfony\Component\Validator\DefaultTranslator;
use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Validator\NodeValidator;
use Symfony\Component\Validator\Validator\Validator; use Symfony\Component\Validator\Validator\Validator;
class TraversingValidatorTest extends AbstractValidatorTest class TraversingValidatorTest extends AbstractValidatorTest
{ {
protected function createValidator(MetadataFactoryInterface $metadataFactory) protected function createValidator(MetadataFactoryInterface $metadataFactory)
{ {
$validatorFactory = new ConstraintValidatorFactory();
$nodeTraverser = new NodeTraverser($metadataFactory); $nodeTraverser = new NodeTraverser($metadataFactory);
$nodeValidator = new NodeValidator($validatorFactory, $nodeTraverser); $nodeValidator = new NodeValidator($nodeTraverser, new ConstraintValidatorFactory());
$contextManager = new ExecutionContextManager($metadataFactory, $nodeValidator, new DefaultTranslator()); $contextManager = new ExecutionContextManager($nodeValidator, new DefaultTranslator());
$validator = new Validator($nodeTraverser, $metadataFactory, $contextManager); $validator = new Validator($nodeTraverser, $metadataFactory, $contextManager);
// The context manager needs the validator for passing it to created
// contexts
$contextManager->initialize($validator); $contextManager->initialize($validator);
$nodeValidator->setContextManager($contextManager);
// The node validator needs the context manager for passing the current
// context to the constraint validators
$nodeValidator->initialize($contextManager);
$nodeTraverser->addVisitor($contextManager); $nodeTraverser->addVisitor($contextManager);
$nodeTraverser->addVisitor($nodeValidator); $nodeTraverser->addVisitor($nodeValidator);

View File

@ -16,10 +16,10 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Mapping\ClassMetadataInterface; use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
use Symfony\Component\Validator\Mapping\ValueMetadata; use Symfony\Component\Validator\Mapping\ValueMetadata;
use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\NodeTraverser\ClassNode; use Symfony\Component\Validator\Node\ClassNode;
use Symfony\Component\Validator\Node\PropertyNode;
use Symfony\Component\Validator\Node\ValueNode;
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface; use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
use Symfony\Component\Validator\NodeTraverser\PropertyNode;
use Symfony\Component\Validator\NodeTraverser\ValueNode;
/** /**
* @since %%NextVersion%% * @since %%NextVersion%%
@ -60,9 +60,14 @@ abstract class AbstractValidator implements ValidatorInterface
return new ContextualValidator($this->nodeTraverser, $this->metadataFactory, $context); return new ContextualValidator($this->nodeTraverser, $this->metadataFactory, $context);
} }
public function getMetadataFactory() public function getMetadataFor($object)
{ {
return $this->metadataFactory; return $this->metadataFactory->getMetadataFor($object);
}
public function hasMetadataFor($object)
{
return $this->metadataFactory->hasMetadataFor($object);
} }
protected function traverseObject($object, $groups = null) protected function traverseObject($object, $groups = null)

View File

@ -26,7 +26,7 @@ use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
*/ */
class NodeValidator extends AbstractVisitor implements GroupManagerInterface class NodeValidator extends AbstractVisitor implements GroupManagerInterface
{ {
private $validatedNodes = array(); private $validatedObjects = array();
/** /**
* @var ConstraintValidatorFactoryInterface * @var ConstraintValidatorFactoryInterface
@ -45,25 +45,25 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
private $currentGroup; private $currentGroup;
public function __construct(ConstraintValidatorFactoryInterface $validatorFactory, NodeTraverserInterface $nodeTraverser) public function __construct(NodeTraverserInterface $nodeTraverser, ConstraintValidatorFactoryInterface $validatorFactory)
{ {
$this->validatorFactory = $validatorFactory; $this->validatorFactory = $validatorFactory;
$this->nodeTraverser = $nodeTraverser; $this->nodeTraverser = $nodeTraverser;
} }
public function setContextManager(ExecutionContextManagerInterface $contextManager) public function initialize(ExecutionContextManagerInterface $contextManager)
{ {
$this->contextManager = $contextManager; $this->contextManager = $contextManager;
} }
public function afterTraversal(array $nodes) public function afterTraversal(array $nodes)
{ {
$this->validatedNodes = array(); $this->validatedObjects = array();
} }
public function enterNode(Node $node) public function enterNode(Node $node)
{ {
$cacheKey = $node instanceof ClassNode $objectHash = $node instanceof ClassNode
? spl_object_hash($node->value) ? spl_object_hash($node->value)
: null; : null;
@ -75,73 +75,38 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
foreach ($node->groups as $group) { foreach ($node->groups as $group) {
// Validate object nodes only once per group // Validate object nodes only once per group
if (null !== $cacheKey) { if (null !== $objectHash) {
// Use the object hash for group sequences // Use the object hash for group sequences
$groupKey = is_object($group) ? spl_object_hash($group) : $group; $groupHash = is_object($group) ? spl_object_hash($group) : $group;
// Exit, if the object is already validated for the current group // Exit, if the object is already validated for the current group
if (isset($this->validatedNodes[$cacheKey][$groupKey])) { if (isset($this->validatedObjects[$objectHash][$groupHash])) {
return false; return false;
} }
// Remember validating this object before starting and possibly // Remember validating this object before starting and possibly
// traversing the object graph // traversing the object graph
$this->validatedNodes[$cacheKey][$groupKey] = true; $this->validatedObjects[$objectHash][$groupHash] = true;
} }
// Validate group sequence until a violation is generated // Validate group sequence until a violation is generated
if ($group instanceof GroupSequence) { if (!$group instanceof GroupSequence) {
// Rename for clarity $this->validateNodeForGroup($node, $group);
$groupSequence = $group;
// Only evaluate group sequences at class, not at property level
if (!$node instanceof ClassNode) {
continue;
}
$context = $this->contextManager->getCurrentContext();
$violationCount = count($context->getViolations());
foreach ($groupSequence->groups as $groupInSequence) {
$this->nodeTraverser->traverse(array(new ClassNode(
$node->value,
$node->metadata,
$node->propertyPath,
array($groupInSequence),
array($groupSequence->cascadedGroup ?: $groupInSequence)
)));
// Abort sequence validation if a violation was generated
if (count($context->getViolations()) > $violationCount) {
break;
}
}
// Optimization: If the groups only contain the group sequence,
// we can skip the traversal for the properties of the object
if (1 === count($node->groups)) {
return false;
}
// We're done for the current loop execution.
continue; continue;
} }
// Validate normal group (non group sequences) // Only traverse group sequences at class, not at property level
try { if (!$node instanceof ClassNode) {
$this->currentGroup = $group; continue;
}
foreach ($node->metadata->findConstraints($group) as $constraint) { $this->traverseGroupSequence($node, $group);
$validator = $this->validatorFactory->getInstance($constraint);
$validator->initialize($this->contextManager->getCurrentContext());
$validator->validate($node->value, $constraint);
}
$this->currentGroup = null; // Optimization: If the groups only contain the group sequence,
} catch (\Exception $e) { // we can skip the traversal for the properties of the object
$this->currentGroup = null; if (1 === count($node->groups)) {
return false;
throw $e;
} }
} }
@ -152,4 +117,50 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
{ {
return $this->currentGroup; return $this->currentGroup;
} }
private function traverseGroupSequence(ClassNode $node, GroupSequence $groupSequence)
{
$context = $this->contextManager->getCurrentContext();
$violationCount = count($context->getViolations());
foreach ($groupSequence->groups as $groupInSequence) {
$this->nodeTraverser->traverse(array(new ClassNode(
$node->value,
$node->metadata,
$node->propertyPath,
array($groupInSequence),
array($groupSequence->cascadedGroup ?: $groupInSequence)
)));
// Abort sequence validation if a violation was generated
if (count($context->getViolations()) > $violationCount) {
break;
}
}
}
/**
* @param Node $node
* @param $group
*
* @throws \Exception
*/
private function validateNodeForGroup(Node $node, $group)
{
try {
$this->currentGroup = $group;
foreach ($node->metadata->findConstraints($group) as $constraint) {
$validator = $this->validatorFactory->getInstance($constraint);
$validator->initialize($this->contextManager->getCurrentContext());
$validator->validate($node->value, $constraint);
}
$this->currentGroup = null;
} catch (\Exception $e) {
$this->currentGroup = null;
throw $e;
}
}
} }

View File

@ -85,4 +85,8 @@ interface ValidatorInterface
* @return ContextualValidatorInterface * @return ContextualValidatorInterface
*/ */
public function inContext(ExecutionContextInterface $context); public function inContext(ExecutionContextInterface $context);
public function getMetadataFor($object);
public function hasMetadataFor($object);
} }