[Validator] Prototype of the traverser implementation

This commit is contained in:
Bernhard Schussek 2014-02-17 14:12:00 +01:00
parent 25cdc68d36
commit a6ed4cae5d
27 changed files with 1851 additions and 0 deletions

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Validator\Constraints;
use Traversable;
/**
* Annotation for group sequences
*
@ -28,6 +30,22 @@ class GroupSequence implements \ArrayAccess, \IteratorAggregate, \Countable
*/
public $groups;
/**
* The group under which cascaded objects are validated when validating
* this sequence.
*
* By default, cascaded objects are validated in each of the groups of
* the sequence.
*
* If a class has a group sequence attached, that sequence replaces the
* "Default" group. When validating that class in the "Default" group, the
* group sequence is used instead, but still the "Default" group should be
* cascaded to other objects.
*
* @var string|GroupSequence
*/
public $cascadedGroup;
public function __construct(array $groups)
{
// Support for Doctrine annotations

View File

@ -0,0 +1,174 @@
<?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\ClassBasedInterface;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Group\GroupManagerInterface;
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Node\Node;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ExecutionContext implements ExecutionContextInterface
{
private $root;
private $violations;
/**
* @var Node
*/
private $node;
/**
* @var \SplStack
*/
private $nodeStack;
/**
* @var MetadataFactoryInterface
*/
private $metadataFactory;
/**
* @var ValidatorInterface
*/
private $validator;
/**
* @var GroupManagerInterface
*/
private $groupManager;
public function __construct(MetadataFactoryInterface $metadataFactory, ValidatorInterface $validator, GroupManagerInterface $groupManager)
{
$this->metadataFactory = $metadataFactory;
$this->validator = $validator;
$this->groupManager = $groupManager;
$this->violations = new ConstraintViolationList();
}
public function pushNode(Node $node)
{
if (null === $this->node) {
$this->root = $node->value;
} else {
$this->nodeStack->push($this->node);
}
$this->node = $node;
}
public function popNode()
{
$poppedNode = $this->node;
if (0 === count($this->nodeStack)) {
$this->node = null;
return $poppedNode;
}
if (1 === count($this->nodeStack)) {
$this->nodeStack->pop();
$this->node = null;
return $poppedNode;
}
$this->nodeStack->pop();
$this->node = $this->nodeStack->top();
return $poppedNode;
}
public function addViolation($message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
{
}
public function buildViolation($message)
{
}
public function getMetadataFor($object)
{
}
public function getViolations()
{
return $this->violations;
}
public function getRoot()
{
return $this->root;
}
public function getValue()
{
return $this->node ? $this->node->value : null;
}
public function getMetadata()
{
return $this->node ? $this->node->metadata : null;
}
public function getGroup()
{
return $this->groupManager->getCurrentGroup();
}
public function getClassName()
{
$metadata = $this->getMetadata();
return $metadata instanceof ClassBasedInterface ? $metadata->getClassName() : null;
}
public function getPropertyName()
{
$metadata = $this->getMetadata();
return $metadata instanceof PropertyMetadataInterface ? $metadata->getPropertyName() : null;
}
public function getPropertyPath($subPath = '')
{
$propertyPath = $this->node ? $this->node->propertyPath : '';
if (strlen($subPath) > 0) {
if ('[' === $subPath{1}) {
return $propertyPath.$subPath;
}
return $propertyPath ? $propertyPath.'.'.$subPath : $subPath;
}
return $propertyPath;
}
/**
* @return ValidatorInterface
*/
public function getValidator()
{
return $this->validator;
}
}

View File

@ -0,0 +1,154 @@
<?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\ConstraintViolationListInterface;
use Symfony\Component\Validator\Mapping\MetadataInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ExecutionContextInterface
{
/**
* @return ValidatorInterface
*/
public function getValidator();
/**
* Adds a violation at the current node of the validation graph.
*
* @param string $message The error message.
* @param array $params The parameters substituted in the error message.
*
* @api
*/
public function addViolation($message, array $params = array());
public function buildViolation($message);
/**
* Returns the violations generated by the validator so far.
*
* @return ConstraintViolationListInterface The constraint violation list.
*
* @api
*/
public function getViolations();
/**
* Returns the value at which validation was started in the object graph.
*
* The validator, when given an object, traverses the properties and
* related objects and their properties. The root of the validation is the
* object from which the traversal started.
*
* The current value is returned by {@link getValue}.
*
* @return mixed The root value of the validation.
*/
public function getRoot();
/**
* Returns the value that the validator is currently validating.
*
* If you want to retrieve the object that was originally passed to the
* validator, use {@link getRoot}.
*
* @return mixed The currently validated value.
*/
public function getValue();
/**
* Returns the metadata for the currently validated value.
*
* With the core implementation, this method returns a
* {@link Mapping\ClassMetadata} instance if the current value is an object,
* a {@link Mapping\PropertyMetadata} instance if the current value is
* the value of a property and a {@link Mapping\GetterMetadata} instance if
* the validated value is the result of a getter method.
*
* If the validated value is neither of these, for example if the validator
* has been called with a plain value and constraint, this method returns
* null.
*
* @return MetadataInterface|null The metadata of the currently validated
* value.
*/
public function getMetadata();
public function getMetadataFor($object);
/**
* Returns the validation group that is currently being validated.
*
* @return string The current validation group.
*/
public function getGroup();
/**
* Returns the class name of the current node.
*
* If the metadata of the current node does not implement
* {@link ClassBasedInterface} or if no metadata is available for the
* current node, this method returns null.
*
* @return string|null The class name or null, if no class name could be found.
*/
public function getClassName();
/**
* Returns the property name of the current node.
*
* If the metadata of the current node does not implement
* {@link PropertyMetadataInterface} or if no metadata is available for the
* current node, this method returns null.
*
* @return string|null The property name or null, if no property name could be found.
*/
public function getPropertyName();
/**
* Returns the property path to the value that the validator is currently
* validating.
*
* For example, take the following object graph:
*
* <pre>
* (Person)---($address: Address)---($street: string)
* </pre>
*
* When the <tt>Person</tt> instance is passed to the validator, the
* property path is initially empty. When the <tt>$address</tt> property
* of that person is validated, the property path is "address". When
* the <tt>$street</tt> property of the related <tt>Address</tt> instance
* is validated, the property path is "address.street".
*
* Properties of objects are prefixed with a dot in the property path.
* Indices of arrays or objects implementing the {@link \ArrayAccess}
* interface are enclosed in brackets. For example, if the property in
* the previous example is <tt>$addresses</tt> and contains an array
* of <tt>Address</tt> instance, the property path generated for the
* <tt>$street</tt> property of one of these addresses is for example
* "addresses[0].street".
*
* @param string $subPath Optional. The suffix appended to the current
* property path.
*
* @return string The current property path. The result may be an empty
* string if the validator is currently validating the
* root value of the validation graph.
*/
public function getPropertyPath($subPath = '');
}

View File

@ -0,0 +1,130 @@
<?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\Group\GroupManagerInterface;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Node\Node;
use Symfony\Component\Validator\NodeTraverser\AbstractVisitor;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ExecutionContextManager extends AbstractVisitor implements ExecutionContextManagerInterface
{
/**
* @var MetadataFactoryInterface
*/
private $metadataFactory;
/**
* @var GroupManagerInterface
*/
private $groupManager;
/**
* @var ValidatorInterface
*/
private $validator;
/**
* @var ExecutionContext
*/
private $currentContext;
/**
* @var \SplStack|ExecutionContext[]
*/
private $contextStack;
public function __construct(MetadataFactoryInterface $metadataFactory, GroupManagerInterface $groupManager)
{
$this->metadataFactory = $metadataFactory;
$this->groupManager = $groupManager;
$this->reset();
}
public function initialize(ValidatorInterface $validator)
{
$this->validator = $validator;
}
public function startContext()
{
if (null !== $this->currentContext) {
$this->contextStack->push($this->currentContext);
}
$this->currentContext = new ExecutionContext($this->metadataFactory, $this->validator, $this->groupManager);
return $this->currentContext;
}
public function stopContext()
{
$stoppedContext = $this->currentContext;
if (0 === count($this->contextStack)) {
$this->currentContext = null;
return $stoppedContext;
}
if (1 === count($this->contextStack)) {
$this->contextStack->pop();
$this->currentContext = null;
return $stoppedContext;
}
$this->contextStack->pop();
$this->currentContext = $this->contextStack->top();
return $stoppedContext;
}
public function getCurrentContext()
{
return $this->currentContext;
}
public function afterTraversal(array $nodes)
{
$this->reset();
}
public function enterNode(Node $node)
{
if (null === $this->currentContext) {
// error no context started
}
$this->currentContext->pushNode($node);
}
public function leaveNode(Node $node)
{
if (null === $this->currentContext) {
// error no context started
}
$this->currentContext->popNode();
}
private function reset()
{
$this->contextStack = new \SplStack();
}
}

View File

@ -0,0 +1,34 @@
<?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;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ExecutionContextManagerInterface
{
/**
* @return ExecutionContextInterface The started context
*/
public function startContext();
/**
* @return ExecutionContextInterface The stopped context
*/
public function stopContext();
/**
* @return ExecutionContextInterface The current context
*/
public function getCurrentContext();
}

View File

@ -0,0 +1,41 @@
<?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\ExecutionContextInterface as LegacyExecutionContextInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LegacyExecutionContext extends ExecutionContext implements LegacyExecutionContextInterface
{
public function addViolationAt($subPath, $message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
{
}
public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false)
{
}
public function validateValue($value, $constraints, $subPath = '', $groups = null)
{
}
public function getMetadataFactory()
{
}
}

View File

@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Group;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface GroupManagerInterface
{
public function getCurrentGroup();
}

View File

@ -0,0 +1,44 @@
<?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\Mapping;
use Symfony\Component\Validator\ClassBasedInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ClassMetadataInterface extends MetadataInterface, ClassBasedInterface
{
public function getConstrainedProperties();
public function hasPropertyMetadata($property);
/**
* Returns all metadata instances for the given named property.
*
* If your implementation does not support properties, simply throw an
* exception in this method (for example a <tt>BadMethodCallException</tt>).
*
* @param string $property The property name.
*
* @return PropertyMetadataInterface[] A list of metadata instances. Empty if
* no metadata exists for the property.
*/
public function getPropertyMetadata($property);
public function hasGroupSequence();
public function getGroupSequence();
public function isGroupSequenceProvider();
}

View File

@ -0,0 +1,57 @@
<?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\Mapping;
/**
* A container for validation metadata.
*
* The container contains constraints that may belong to different validation
* groups. Constraints for a specific group can be fetched by calling
* {@link findConstraints}.
*
* Implement this interface to add validation metadata to your own metadata
* layer. Each metadata may have named properties. Each property can be
* represented by one or more {@link PropertyMetadataInterface} instances that
* are returned by {@link getPropertyMetadata}. Since
* <tt>PropertyMetadataInterface</tt> inherits from <tt>MetadataInterface</tt>,
* each property may be divided into further properties.
*
* The {@link accept} method of each metadata implements the Visitor pattern.
* The method should forward the call to the visitor's
* {@link ValidationVisitorInterface::visit} method and additionally call
* <tt>accept()</tt> on all structurally related metadata instances.
*
* For example, to store constraints for PHP classes and their properties,
* create a class <tt>ClassMetadata</tt> (implementing <tt>MetadataInterface</tt>)
* and a class <tt>PropertyMetadata</tt> (implementing <tt>PropertyMetadataInterface</tt>).
* <tt>ClassMetadata::getPropertyMetadata($property)</tt> returns all
* <tt>PropertyMetadata</tt> instances for a property of that class. Its
* <tt>accept()</tt>-method simply forwards to <tt>ValidationVisitorInterface::visit()</tt>
* and calls <tt>accept()</tt> on all contained <tt>PropertyMetadata</tt>
* instances, which themselves call <tt>ValidationVisitorInterface::visit()</tt>
* again.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface MetadataInterface
{
/**
* Returns all constraints for a given validation group.
*
* @param string $group The validation group.
*
* @return \Symfony\Component\Validator\Constraint[] A list of constraint instances.
*/
public function findConstraints($group);
public function supportsCascading();
}

View File

@ -0,0 +1,46 @@
<?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\Mapping;
use Symfony\Component\Validator\ClassBasedInterface;
/**
* A container for validation metadata of a property.
*
* What exactly you define as "property" is up to you. The validator expects
* implementations of {@link MetadataInterface} that contain constraints and
* optionally a list of named properties that also have constraints (and may
* have further sub properties). Such properties are mapped by implementations
* of this interface.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see MetadataInterface
*/
interface PropertyMetadataInterface extends MetadataInterface, ClassBasedInterface
{
/**
* Returns the name of the property.
*
* @return string The property name.
*/
public function getPropertyName();
/**
* Extracts the value of the property from the given object.
*
* @param mixed $object The object to extract the property value from.
*
* @return mixed The value of the property.
*/
public function getPropertyValue($object);
}

View File

@ -0,0 +1,46 @@
<?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\Mapping;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ValueMetadata implements MetadataInterface
{
/**
* Returns all constraints for a given validation group.
*
* @param string $group The validation group.
*
* @return \Symfony\Component\Validator\Constraint[] A list of constraint instances.
*/
public function findConstraints($group)
{
}
public function supportsCascading()
{
}
public function supportsIteration()
{
}
public function supportsRecursiveIteration()
{
}
}

View File

@ -0,0 +1,41 @@
<?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\Node;
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ClassNode extends Node
{
/**
* @var ClassMetadataInterface
*/
public $metadata;
public function __construct($value, ClassMetadataInterface $metadata, $propertyPath, array $groups)
{
if (!is_object($value)) {
// error
}
parent::__construct(
$value,
$metadata,
$propertyPath,
$groups
);
}
}

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\Node;
use Symfony\Component\Validator\Mapping\MetadataInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class Node
{
public $value;
public $metadata;
public $propertyPath;
public $groups;
public function __construct($value, MetadataInterface $metadata, $propertyPath, array $groups)
{
$this->value = $value;
$this->metadata = $metadata;
$this->propertyPath = $propertyPath;
$this->groups = $groups;
}
}

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\Node;
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class PropertyNode extends Node
{
/**
* @var PropertyMetadataInterface
*/
public $metadata;
public function __construct($value, PropertyMetadataInterface $metadata, $propertyPath, array $groups)
{
parent::__construct(
$value,
$metadata,
$propertyPath,
$groups
);
}
}

View File

@ -0,0 +1,20 @@
<?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\Node;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ValueNode extends Node
{
}

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\NodeTraverser;
use Symfony\Component\Validator\Node\Node;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class AbstractVisitor implements NodeVisitorInterface
{
public function beforeTraversal(array $nodes)
{
}
public function afterTraversal(array $nodes)
{
}
public function enterNode(Node $node)
{
}
public function leaveNode(Node $node)
{
}
}

View File

@ -0,0 +1,166 @@
<?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\NodeTraverser;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Context\ExecutionContextManagerInterface;
use Symfony\Component\Validator\MetadataFactoryInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class NodeTraverser implements NodeTraverserInterface
{
/**
* @var NodeVisitorInterface[]
*/
private $visitors;
/**
* @var MetadataFactoryInterface
*/
private $metadataFactory;
private $traversalStarted = false;
public function __construct(MetadataFactoryInterface $metadataFactory)
{
$this->visitors = new \SplObjectStorage();
$this->metadataFactory = $metadataFactory;
}
public function addVisitor(NodeVisitorInterface $visitor)
{
$this->visitors->attach($visitor);
}
public function removeVisitor(NodeVisitorInterface $visitor)
{
$this->visitors->detach($visitor);
}
/**
* {@inheritdoc}
*/
public function traverse(array $nodes)
{
$isTopLevelCall = !$this->traversalStarted;
if ($isTopLevelCall) {
$this->traversalStarted = true;
foreach ($this->visitors as $visitor) {
/** @var NodeVisitorInterface $visitor */
$visitor->beforeTraversal($nodes);
}
}
foreach ($nodes as $node) {
if ($node instanceof ClassNode) {
$this->traverseClassNode($node);
} else {
$this->traverseNode($node);
}
}
if ($isTopLevelCall) {
$this->traversalStarted = false;
foreach ($this->visitors as $visitor) {
/** @var NodeVisitorInterface $visitor */
$visitor->afterTraversal($nodes);
}
}
}
private function traverseNode(Node $node)
{
$stopTraversal = false;
foreach ($this->visitors as $visitor) {
if (false === $visitor->enterNode($node)) {
$stopTraversal = true;
}
}
// Stop the traversal, but execute the leaveNode() methods anyway to
// perform possible cleanups
if (!$stopTraversal && is_object($node->value) && $node->metadata->supportsCascading()) {
$classMetadata = $this->metadataFactory->getMetadataFor($node->value);
$this->traverseClassNode(new ClassNode(
$node->value,
$classMetadata,
$node->propertyPath,
$node->groups
));
}
foreach ($this->visitors as $visitor) {
$visitor->leaveNode($node);
}
}
private function traverseClassNode(ClassNode $node)
{
// Replace "Default" group by the group sequence attached to the class
// (if any)
foreach ($node->groups as $key => $group) {
if (Constraint::DEFAULT_GROUP !== $group) {
continue;
}
if ($node->metadata->hasGroupSequence()) {
$node->groups[$key] = $node->metadata->getGroupSequence();
} elseif ($node->metadata->isGroupSequenceProvider()) {
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
$node->groups[$key] = $value->getGroupSequence();
}
// Cascade the "Default" group when validating the sequence
$node->groups[$key]->cascadedGroup = Constraint::DEFAULT_GROUP;
// "Default" group found, abort
break;
}
$stopTraversal = false;
foreach ($this->visitors as $visitor) {
if (false === $visitor->enterNode($node)) {
$stopTraversal = true;
}
}
// Stop the traversal, but execute the leaveNode() methods anyway to
// perform possible cleanups
if (!$stopTraversal) {
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
));
}
}
}
foreach ($this->visitors as $visitor) {
$visitor->leaveNode($node);
}
}
}

View File

@ -0,0 +1,32 @@
<?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\NodeTraverser;
use Symfony\Component\Validator\Node\Node;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface NodeTraverserInterface
{
public function addVisitor(NodeVisitorInterface $visitor);
public function removeVisitor(NodeVisitorInterface $visitor);
/**
* @param Node[] $nodes
*
* @return mixed
*/
public function traverse(array $nodes);
}

View File

@ -0,0 +1,29 @@
<?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\NodeTraverser;
use Symfony\Component\Validator\Node\Node;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface NodeVisitorInterface
{
public function beforeTraversal(array $nodes);
public function afterTraversal(array $nodes);
public function enterNode(Node $node);
public function leaveNode(Node $node);
}

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Tests\Validator;
use Symfony\Component\Validator\Context\ExecutionContextManager;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Tests\AbstractValidatorTest;
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
use Symfony\Component\Validator\NodeTraverser\NodeVisitor\NodeValidator;
use Symfony\Component\Validator\DefaultTranslator;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Validator\Validator;
class TraversingValidatorTest extends AbstractValidatorTest
{
protected function createValidator(MetadataFactoryInterface $metadataFactory)
{
$validatorFactory = new ConstraintValidatorFactory();
$nodeTraverser = new NodeTraverser($metadataFactory);
$nodeValidator = new NodeValidator($validatorFactory, $nodeTraverser);
$contextManager = new ExecutionContextManager($metadataFactory, $nodeValidator, new DefaultTranslator());
$validator = new Validator($nodeTraverser, $metadataFactory, $contextManager);
$contextManager->initialize($validator);
$nodeValidator->setContextManager($contextManager);
$nodeTraverser->addVisitor($contextManager);
$nodeTraverser->addVisitor($nodeValidator);
return $validator;
}
}

View File

@ -0,0 +1,155 @@
<?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\Mapping\ClassMetadataInterface;
use Symfony\Component\Validator\Mapping\ValueMetadata;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\NodeTraverser\ClassNode;
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
use Symfony\Component\Validator\NodeTraverser\PropertyNode;
use Symfony\Component\Validator\NodeTraverser\ValueNode;
/**
* @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 getMetadataFactory()
{
return $this->metadataFactory;
}
protected function traverseObject($object, $groups = null)
{
$classMetadata = $this->metadataFactory->getMetadataFor($object);
if (!$classMetadata instanceof ClassMetadataInterface) {
// error
}
$this->nodeTraverser->traverse(array(new ClassNode(
$object,
$classMetadata,
$this->defaultPropertyPath,
// TODO use cascade group here
$groups ? $this->normalizeGroups($groups) : $this->defaultGroups
)));
}
protected function traverseProperty($object, $propertyName, $groups = null)
{
$classMetadata = $this->metadataFactory->getMetadataFor($object);
if (!$classMetadata instanceof ClassMetadataInterface) {
// error
}
$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,
$this->defaultPropertyPath,
$groups
);
}
$this->nodeTraverser->traverse($nodes);
}
protected function traversePropertyValue($object, $propertyName, $value, $groups = null)
{
$classMetadata = $this->metadataFactory->getMetadataFor($object);
if (!$classMetadata instanceof ClassMetadataInterface) {
// error
}
$propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
$nodes = array();
foreach ($propertyMetadatas as $propertyMetadata) {
$nodes[] = new PropertyNode(
$value,
$propertyMetadata,
$this->defaultPropertyPath,
$groups
);
}
$this->nodeTraverser->traverse($nodes);
}
protected function traverseValue($value, $constraints, $groups = null)
{
$metadata = new ValueMetadata($constraints);
$this->nodeTraverser->traverse(array(new ValueNode(
$value,
$metadata,
$this->defaultPropertyPath,
$groups ? $this->normalizeGroups($groups) : $this->defaultGroups
)));
}
protected function normalizeGroups($groups)
{
if (is_array($groups)) {
return $groups;
}
return array($groups);
}
}

View File

@ -0,0 +1,123 @@
<?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\ConstraintViolationListInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Context\ExecutionContextManagerInterface;
use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ContextualValidator extends AbstractValidator implements ContextualValidatorInterface
{
/**
* @var ExecutionContextManagerInterface
*/
private $context;
public function __construct(NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory, ExecutionContextInterface $context)
{
parent::__construct($nodeTraverser, $metadataFactory);
$this->context = $context;
$this->defaultPropertyPath = $context->getPropertyPath();
$this->defaultGroups = array($context->getGroup());
}
public function atPath($subPath)
{
$this->defaultPropertyPath = $this->context->getPropertyPath($subPath);
}
/**
* 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);
return $this->context->getViolations();
}
/**
* 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);
return $this->context->getViolations();
}
/**
* 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);
return $this->context->getViolations();
}
/**
* 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 validateValue($value, $constraints, $groups = null)
{
$this->traverseValue($value, $constraints, $groups);
return $this->context->getViolations();
}
}

View File

@ -0,0 +1,26 @@
<?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;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ContextualValidatorInterface extends ValidatorInterface
{
/**
* @param $subPath
*
* @return ContextualValidatorInterface
*/
public function atPath($subPath);
}

View File

@ -0,0 +1,27 @@
<?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\ValidatorInterface as LegacyValidatorInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LegacyValidator extends Validator implements LegacyValidatorInterface
{
public function validate($value, $groups = null, $traverse = false, $deep = false)
{
// TODO what about $traverse and $deep?
return $this->validateObject($value, $groups);
}
}

View File

@ -0,0 +1,155 @@
<?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\Constraints\GroupSequence;
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
use Symfony\Component\Validator\Context\ExecutionContextManagerInterface;
use Symfony\Component\Validator\Group\GroupManagerInterface;
use Symfony\Component\Validator\Node\ClassNode;
use Symfony\Component\Validator\Node\Node;
use Symfony\Component\Validator\NodeTraverser\AbstractVisitor;
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class NodeValidator extends AbstractVisitor implements GroupManagerInterface
{
private $validatedNodes = array();
/**
* @var ConstraintValidatorFactoryInterface
*/
private $validatorFactory;
/**
* @var ExecutionContextManagerInterface
*/
private $contextManager;
/**
* @var NodeTraverserInterface
*/
private $nodeTraverser;
private $currentGroup;
public function __construct(ConstraintValidatorFactoryInterface $validatorFactory, NodeTraverserInterface $nodeTraverser)
{
$this->validatorFactory = $validatorFactory;
$this->nodeTraverser = $nodeTraverser;
}
public function setContextManager(ExecutionContextManagerInterface $contextManager)
{
$this->contextManager = $contextManager;
}
public function afterTraversal(array $nodes)
{
$this->validatedNodes = array();
}
public function enterNode(Node $node)
{
$cacheKey = $node instanceof ClassNode
? spl_object_hash($node->value)
: null;
// if group (=[<G1,G2>,G3,G4]) contains group sequence (=<G1,G2>)
// then call traverse() with each entry of the group sequence and abort
// if necessary (G1, G2)
// finally call traverse() with remaining entries ([G3,G4]) or
// simply continue traversal (if possible)
foreach ($node->groups as $group) {
// Validate object nodes only once per group
if (null !== $cacheKey) {
// Use the object hash for group sequences
$groupKey = is_object($group) ? spl_object_hash($group) : $group;
// Exit, if the object is already validated for the current group
if (isset($this->validatedNodes[$cacheKey][$groupKey])) {
return false;
}
// Remember validating this object before starting and possibly
// traversing the object graph
$this->validatedNodes[$cacheKey][$groupKey] = true;
}
// Validate group sequence until a violation is generated
if ($group instanceof GroupSequence) {
// Rename for clarity
$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;
}
// Validate normal group (non group sequences)
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;
}
}
return true;
}
public function getCurrentGroup()
{
return $this->currentGroup;
}
}

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\Validator;
use Symfony\Component\Validator\Context\ExecutionContextManagerInterface;
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
use Symfony\Component\Validator\MetadataFactoryInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class Validator extends AbstractValidator
{
/**
* @var ExecutionContextManagerInterface
*/
private $contextManager;
public function __construct(NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory, ExecutionContextManagerInterface $contextManager)
{
parent::__construct($nodeTraverser, $metadataFactory);
$this->contextManager = $contextManager;
}
public function validateObject($object, $groups = null)
{
$this->contextManager->startContext();
$this->traverseObject($object, $groups);
return $this->contextManager->stopContext()->getViolations();
}
public function validateProperty($object, $propertyName, $groups = null)
{
$this->contextManager->startContext();
$this->traverseProperty($object, $propertyName, $groups);
return $this->contextManager->stopContext()->getViolations();
}
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
{
$this->contextManager->startContext();
$this->traversePropertyValue($object, $propertyName, $value, $groups);
return $this->contextManager->stopContext()->getViolations();
}
public function validateValue($value, $constraints, $groups = null)
{
$this->contextManager->startContext();
$this->traverseValue($value, $constraints, $groups);
return $this->contextManager->stopContext()->getViolations();
}
}

View File

@ -0,0 +1,88 @@
<?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\ConstraintViolationListInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* @since %%NextVersion%%
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface ValidatorInterface
{
/**
* 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);
/**
* 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);
/**
* 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);
/**
* 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 validateValue($value, $constraints, $groups = null);
/**
* @param ExecutionContextInterface $context
*
* @return ContextualValidatorInterface
*/
public function inContext(ExecutionContextInterface $context);
}