[Validator] Removed traverser implementation
The traverser is too slow compared to the current, recursive approach. Testing showed a performance decrease of about 70% without a lot of optimization potential.
This commit is contained in:
parent
3183aed7cd
commit
90c27bb1e7
|
@ -1,74 +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\Node;
|
||||
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
|
||||
/**
|
||||
* Represents an object and its class metadata in the validation graph.
|
||||
*
|
||||
* If the object is a collection which should be traversed, a new
|
||||
* {@link CollectionNode} instance will be created for that object:
|
||||
*
|
||||
* (TagList:ClassNode)
|
||||
* \
|
||||
* (TagList:CollectionNode)
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ClassNode extends Node
|
||||
{
|
||||
/**
|
||||
* @var ClassMetadataInterface
|
||||
*/
|
||||
public $metadata;
|
||||
|
||||
/**
|
||||
* Creates a new class node.
|
||||
*
|
||||
* @param object $object The validated object
|
||||
* @param ClassMetadataInterface $metadata The class metadata of
|
||||
* that object
|
||||
* @param string $propertyPath The property path leading
|
||||
* to this node
|
||||
* @param string[] $groups The groups in which this
|
||||
* node should be validated
|
||||
* @param string[]|null $cascadedGroups The groups in which
|
||||
* cascaded objects should
|
||||
* be validated
|
||||
* @param integer $traversalStrategy The strategy used for
|
||||
* traversing the object
|
||||
*
|
||||
* @throws UnexpectedTypeException If $object is not an object
|
||||
*
|
||||
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||
*/
|
||||
public function __construct($object, $cacheKey, ClassMetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||
{
|
||||
if (!is_object($object)) {
|
||||
throw new UnexpectedTypeException($object, 'object');
|
||||
}
|
||||
|
||||
parent::__construct(
|
||||
$object,
|
||||
$cacheKey,
|
||||
$metadata,
|
||||
$propertyPath,
|
||||
$groups,
|
||||
$cascadedGroups,
|
||||
$traversalStrategy
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,66 +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\Node;
|
||||
|
||||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
|
||||
/**
|
||||
* Represents a traversable value in the validation graph.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class CollectionNode extends Node
|
||||
{
|
||||
/**
|
||||
* Creates a new collection node.
|
||||
*
|
||||
* @param array|\Traversable $collection The validated collection
|
||||
* @param string $propertyPath The property path leading
|
||||
* to this node
|
||||
* @param string[] $groups The groups in which this
|
||||
* node should be validated
|
||||
* @param string[]|null $cascadedGroups The groups in which
|
||||
* cascaded objects should be
|
||||
* validated
|
||||
* @param integer $traversalStrategy The strategy used for
|
||||
* traversing the collection
|
||||
*
|
||||
* @throws ConstraintDefinitionException If $collection is not an array or a
|
||||
* \Traversable
|
||||
*
|
||||
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||
*/
|
||||
public function __construct($collection, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::TRAVERSE)
|
||||
{
|
||||
if (!is_array($collection) && !$collection instanceof \Traversable) {
|
||||
// Must throw a ConstraintDefinitionException for backwards
|
||||
// compatibility reasons with Symfony < 2.5
|
||||
throw new ConstraintDefinitionException(sprintf(
|
||||
'Traversal was enabled for "%s", but this class '.
|
||||
'does not implement "\Traversable".',
|
||||
get_class($collection)
|
||||
));
|
||||
}
|
||||
|
||||
parent::__construct(
|
||||
$collection,
|
||||
null,
|
||||
null,
|
||||
$propertyPath,
|
||||
$groups,
|
||||
$cascadedGroups,
|
||||
$traversalStrategy
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,26 +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\Node;
|
||||
|
||||
/**
|
||||
* Represents a value that has neither class metadata nor property metadata
|
||||
* attached to it.
|
||||
*
|
||||
* Together with {@link \Symfony\Component\Validator\Mapping\GenericMetadata},
|
||||
* this node type can be used to validate a value against some constraints.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class GenericNode extends Node
|
||||
{
|
||||
}
|
|
@ -1,101 +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\Node;
|
||||
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Mapping\MetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
|
||||
/**
|
||||
* A node in the validation graph.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
abstract class Node
|
||||
{
|
||||
/**
|
||||
* The validated value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $value;
|
||||
|
||||
public $cacheKey;
|
||||
|
||||
/**
|
||||
* The metadata specifying how the value should be validated.
|
||||
*
|
||||
* @var MetadataInterface|null
|
||||
*/
|
||||
public $metadata;
|
||||
|
||||
/**
|
||||
* The property path leading to this node.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $propertyPath;
|
||||
|
||||
/**
|
||||
* The groups in which the value should be validated.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $groups;
|
||||
|
||||
/**
|
||||
* The groups in which cascaded values should be validated.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $cascadedGroups;
|
||||
|
||||
/**
|
||||
* The strategy used for traversing the validated value.
|
||||
*
|
||||
* @var integer
|
||||
*
|
||||
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||
*/
|
||||
public $traversalStrategy;
|
||||
|
||||
/**
|
||||
* Creates a new property node.
|
||||
*
|
||||
* @param mixed $value The property value
|
||||
* @param MetadataInterface|null $metadata The property's metadata
|
||||
* @param string $propertyPath The property path leading to
|
||||
* this node
|
||||
* @param string[] $groups The groups in which this node
|
||||
* should be validated
|
||||
* @param string[]|null $cascadedGroups The groups in which cascaded
|
||||
* objects should be validated
|
||||
* @param integer $traversalStrategy
|
||||
*
|
||||
* @throws UnexpectedTypeException If $cascadedGroups is invalid
|
||||
*/
|
||||
public function __construct($value, $cacheKey, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||
{
|
||||
if (null !== $cascadedGroups && !is_array($cascadedGroups)) {
|
||||
throw new UnexpectedTypeException($cascadedGroups, 'null or array');
|
||||
}
|
||||
|
||||
$this->value = $value;
|
||||
$this->cacheKey = $cacheKey;
|
||||
$this->metadata = $metadata;
|
||||
$this->propertyPath = $propertyPath;
|
||||
$this->groups = $groups;
|
||||
$this->cascadedGroups = $cascadedGroups;
|
||||
$this->traversalStrategy = $traversalStrategy;
|
||||
}
|
||||
}
|
|
@ -1,82 +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\Node;
|
||||
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
|
||||
/**
|
||||
* Represents the value of a property and its associated metadata.
|
||||
*
|
||||
* If the property contains an object and should be cascaded, a new
|
||||
* {@link ClassNode} instance will be created for that object:
|
||||
*
|
||||
* (Article:ClassNode)
|
||||
* \
|
||||
* (->author:PropertyNode)
|
||||
* \
|
||||
* (Author:ClassNode)
|
||||
*
|
||||
* If the property contains a collection which should be traversed, a new
|
||||
* {@link CollectionNode} instance will be created for that collection:
|
||||
*
|
||||
* (Article:ClassNode)
|
||||
* \
|
||||
* (->tags:PropertyNode)
|
||||
* \
|
||||
* (array:CollectionNode)
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class PropertyNode extends Node
|
||||
{
|
||||
/**
|
||||
* @var PropertyMetadataInterface
|
||||
*/
|
||||
public $metadata;
|
||||
|
||||
/**
|
||||
* Creates a new property node.
|
||||
*
|
||||
* @param object $object The object the property
|
||||
* belongs to
|
||||
* @param mixed $value The property value
|
||||
* @param PropertyMetadataInterface $metadata The property's metadata
|
||||
* @param string $propertyPath The property path leading
|
||||
* to this node
|
||||
* @param string[] $groups The groups in which this
|
||||
* node should be validated
|
||||
* @param string[]|null $cascadedGroups The groups in which
|
||||
* cascaded objects should
|
||||
* be validated
|
||||
* @param integer $traversalStrategy
|
||||
*
|
||||
* @throws UnexpectedTypeException If $object is not an object
|
||||
*
|
||||
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||
*/
|
||||
public function __construct($value, $cacheKey, PropertyMetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||
{
|
||||
parent::__construct(
|
||||
$value,
|
||||
$cacheKey,
|
||||
$metadata,
|
||||
$propertyPath,
|
||||
$groups,
|
||||
$cascadedGroups,
|
||||
$traversalStrategy
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,99 +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\NodeTraverser;
|
||||
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface;
|
||||
|
||||
/**
|
||||
* Traverses the nodes of the validation graph.
|
||||
*
|
||||
* You can attach visitors to the traverser that are invoked during the
|
||||
* traversal. Before starting the traversal, the
|
||||
* {@link \Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface::beforeTraversal()}
|
||||
* method of each visitor is called. For each node in the graph, the
|
||||
* {@link \Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface::visit()}
|
||||
* of each visitor is called. At the end of the traversal, the traverser invokes
|
||||
* {@link \Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface::afterTraversal()}
|
||||
* on each visitor. The visitors are called in the same order in which they are
|
||||
* added to the traverser.
|
||||
*
|
||||
* If the {@link traverse()} method is called recursively, the
|
||||
* {@link \Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface::beforeTraversal()}
|
||||
* and {@link \Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface::afterTraversal()}
|
||||
* methods of the visitors will be invoked for each call.
|
||||
*
|
||||
* The validation graph typically contains nodes of the following types:
|
||||
*
|
||||
* - {@link \Symfony\Component\Validator\Node\ClassNode}:
|
||||
* An object with associated class metadata
|
||||
* - {@link \Symfony\Component\Validator\Node\PropertyNode}:
|
||||
* A property value with associated property metadata
|
||||
* - {@link \Symfony\Component\Validator\Node\GenericNode}:
|
||||
* A generic value with associated constraints
|
||||
* - {@link \Symfony\Component\Validator\Node\CollectionNode}:
|
||||
* A traversable collection
|
||||
*
|
||||
* Generic nodes are mostly useful when you want to validate a value that has
|
||||
* neither associated class nor property metadata. Generic nodes usually come
|
||||
* with {@link \Symfony\Component\Validator\Mapping\GenericMetadata}, that
|
||||
* contains the constraints that the value should be validated against.
|
||||
*
|
||||
* Whenever a class, property or generic node is validated that contains a
|
||||
* traversable value which should be traversed (according to the
|
||||
* {@link \Symfony\Component\Validator\Mapping\TraversalStrategy} specified
|
||||
* in the node or its metadata), a new
|
||||
* {@link \Symfony\Component\Validator\Node\CollectionNode} will be attached
|
||||
* to the node graph.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* (TagList:ClassNode)
|
||||
* \
|
||||
* (TagList:CollectionNode)
|
||||
*
|
||||
* When writing custom visitors, be aware that collection nodes usually contain
|
||||
* values that have already been passed to the visitor before through a class
|
||||
* node, a property node or a generic node.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
interface NodeTraverserInterface
|
||||
{
|
||||
/**
|
||||
* Adds a new visitor to the traverser.
|
||||
*
|
||||
* Visitors that have already been added before are ignored.
|
||||
*
|
||||
* @param NodeVisitorInterface $visitor The visitor to add
|
||||
*/
|
||||
public function addVisitor(NodeVisitorInterface $visitor);
|
||||
|
||||
/**
|
||||
* Removes a visitor from the traverser.
|
||||
*
|
||||
* Non-existing visitors are ignored.
|
||||
*
|
||||
* @param NodeVisitorInterface $visitor The visitor to remove
|
||||
*/
|
||||
public function removeVisitor(NodeVisitorInterface $visitor);
|
||||
|
||||
/**
|
||||
* Traverses the given nodes in the given context.
|
||||
*
|
||||
* @param Node[] $nodes The nodes to traverse
|
||||
* @param ExecutionContextInterface $context The validation context
|
||||
*/
|
||||
public function traverse($nodes, ExecutionContextInterface $context);
|
||||
}
|
|
@ -1,560 +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\NodeTraverser;
|
||||
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
|
||||
use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
|
||||
use Symfony\Component\Validator\Mapping\CascadingStrategy;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
|
||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\Node\ClassNode;
|
||||
use Symfony\Component\Validator\Node\CollectionNode;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
use Symfony\Component\Validator\Node\PropertyNode;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface;
|
||||
|
||||
/**
|
||||
* Non-recursive implementation of {@link NodeTraverserInterface}.
|
||||
*
|
||||
* This implementation uses a Depth First algorithm to traverse the node
|
||||
* graph. Instead of loading the complete node graph into memory before the
|
||||
* traversal, the traverser only expands the successor nodes of a node once
|
||||
* that node is traversed. For example, when traversing a class node, the
|
||||
* nodes for all constrained properties of that class are loaded into memory.
|
||||
* When the traversal of the class node is over, the node is discarded.
|
||||
*
|
||||
* Next, one of the class' property nodes is traversed. At that point, the
|
||||
* successor nodes of that property node (a class node, if the property should
|
||||
* be cascaded, or a collection node, if the property should be traversed) are
|
||||
* loaded into memory. As soon as the traversal of the property node is over,
|
||||
* it is discarded as well.
|
||||
*
|
||||
* This leads to an average memory consumption of O(log N * B), where N is the
|
||||
* number of nodes in the graph and B is the average number of successor nodes
|
||||
* of a node.
|
||||
*
|
||||
* In order to maintain a small execution stack, nodes are not validated
|
||||
* recursively, but iteratively. Internally, a stack is used to store all the
|
||||
* nodes that should be processed. Whenever a node is traversed, its successor
|
||||
* nodes are put on the stack. The traverser keeps fetching and traversing nodes
|
||||
* from the stack until the stack is empty and all nodes have been traversed.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @see NodeTraverserInterface
|
||||
* @see CascadingStrategy
|
||||
* @see TraversalStrategy
|
||||
*/
|
||||
class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
||||
{
|
||||
/**
|
||||
* @var NodeVisitorInterface[]
|
||||
*/
|
||||
private $visitors;
|
||||
|
||||
/**
|
||||
* @var MetadataFactoryInterface
|
||||
*/
|
||||
private $metadataFactory;
|
||||
|
||||
/**
|
||||
* Creates a new traverser.
|
||||
*
|
||||
* @param MetadataFactoryInterface $metadataFactory The metadata factory
|
||||
*/
|
||||
public function __construct(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$this->visitors = new \SplObjectStorage();
|
||||
$this->nodeStack = new \SplStack();
|
||||
$this->metadataFactory = $metadataFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addVisitor(NodeVisitorInterface $visitor)
|
||||
{
|
||||
$this->visitors->attach($visitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeVisitor(NodeVisitorInterface $visitor)
|
||||
{
|
||||
$this->visitors->detach($visitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function traverse($nodes, ExecutionContextInterface $context)
|
||||
{
|
||||
if (!is_array($nodes)) {
|
||||
$nodes = array($nodes);
|
||||
}
|
||||
|
||||
$numberOfInitializedVisitors = $this->beforeTraversal($nodes, $context);
|
||||
|
||||
// If any of the visitors requested to abort the traversal, do so, but
|
||||
// clean up before
|
||||
if ($numberOfInitializedVisitors < count($this->visitors)) {
|
||||
$this->afterTraversal($nodes, $context, $numberOfInitializedVisitors);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This stack contains all the nodes that should be traversed
|
||||
// A stack is used rather than a queue in order to traverse the graph
|
||||
// in a Depth First approach (the last added node is processed first).
|
||||
// In this way, the order in which the nodes are passed to the visitors
|
||||
// is similar to a recursive implementation (except that the successor
|
||||
// nodes of a node are traversed right-to-left instead of left-to-right).
|
||||
$nodeStack = new \SplStack();
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
// Push a node to the stack and immediately process it. This way,
|
||||
// the successor nodes are traversed before the next node in $nodes
|
||||
$nodeStack->push($node);
|
||||
|
||||
// Fetch nodes from the stack and traverse them until no more nodes
|
||||
// are left. Then continue with the next node in $nodes.
|
||||
while (!$nodeStack->isEmpty()) {
|
||||
$node = $nodeStack->pop();
|
||||
|
||||
if ($node instanceof ClassNode) {
|
||||
$this->traverseClassNode($node, $context, $nodeStack);
|
||||
} elseif ($node instanceof CollectionNode) {
|
||||
$this->traverseCollectionNode($node, $context, $nodeStack);
|
||||
} else {
|
||||
$this->traverseNode($node, $context, $nodeStack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->afterTraversal($nodes, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the {@link NodeVisitorInterface::beforeTraversal()} method of
|
||||
* each visitor.
|
||||
*
|
||||
* @param Node[] $nodes The traversed nodes
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
*
|
||||
* @return integer The number of successful calls. This is lower than
|
||||
* the number of visitors if any of the visitors'
|
||||
* beforeTraversal() methods returned false
|
||||
*/
|
||||
private function beforeTraversal($nodes, ExecutionContextInterface $context)
|
||||
{
|
||||
$numberOfCalls = 1;
|
||||
|
||||
foreach ($this->visitors as $visitor) {
|
||||
if (false === $visitor->beforeTraversal($nodes, $context)) {
|
||||
break;
|
||||
}
|
||||
|
||||
++$numberOfCalls;
|
||||
}
|
||||
|
||||
return $numberOfCalls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the {@link NodeVisitorInterface::beforeTraversal()} method of
|
||||
* each visitor.
|
||||
*
|
||||
* @param Node[] $nodes The traversed nodes
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
* @param integer|null $limit Limits the number of visitors
|
||||
* on which beforeTraversal()
|
||||
* should be called. All visitors
|
||||
* will be called by default
|
||||
*/
|
||||
private function afterTraversal($nodes, ExecutionContextInterface $context, $limit = null)
|
||||
{
|
||||
if (null === $limit) {
|
||||
$limit = count($this->visitors);
|
||||
}
|
||||
|
||||
$numberOfCalls = 0;
|
||||
|
||||
foreach ($this->visitors as $visitor) {
|
||||
$visitor->afterTraversal($nodes, $context);
|
||||
|
||||
if (++$numberOfCalls === $limit) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the {@link NodeVisitorInterface::visit()} method of each
|
||||
* visitor.
|
||||
*
|
||||
* @param Node $node The visited node
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
*
|
||||
* @return Boolean Whether to traverse the node's successor nodes
|
||||
*/
|
||||
private function visit(Node $node, ExecutionContextInterface $context)
|
||||
{
|
||||
foreach ($this->visitors as $visitor) {
|
||||
if (false === $visitor->visit($node, $context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses a class node.
|
||||
*
|
||||
* At first, each visitor is invoked for this node. Then, unless any
|
||||
* of the visitors aborts the traversal by returning false, a property
|
||||
* node is put on the node stack for each constrained property of the class.
|
||||
* At last, if the class is traversable and should be traversed according
|
||||
* to the selected traversal strategy, a new collection node is put on the
|
||||
* stack.
|
||||
*
|
||||
* @param ClassNode $node The class node
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
* @param \SplStack $nodeStack The stack for storing the
|
||||
* successor nodes
|
||||
*
|
||||
* @throws UnsupportedMetadataException If a property metadata does not
|
||||
* implement {@link PropertyMetadataInterface}
|
||||
*
|
||||
* @see ClassNode
|
||||
* @see PropertyNode
|
||||
* @see CollectionNode
|
||||
* @see TraversalStrategy
|
||||
*/
|
||||
private function traverseClassNode(ClassNode $node, ExecutionContextInterface $context, \SplStack $nodeStack)
|
||||
{
|
||||
// Visitors have two possibilities to influence the traversal:
|
||||
//
|
||||
// 1. If a visitor's visit() method returns false, the traversal is
|
||||
// skipped entirely.
|
||||
// 2. If a visitor's visit() method removes a group from the node,
|
||||
// that group will be skipped in the subtree of that node.
|
||||
|
||||
if (false === $this->visit($node, $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 === count($node->groups)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($node->metadata->getConstrainedProperties() as $propertyName) {
|
||||
foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
|
||||
if (!$propertyMetadata instanceof PropertyMetadataInterface) {
|
||||
throw new UnsupportedMetadataException(sprintf(
|
||||
'The property metadata instances should implement '.
|
||||
'"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '.
|
||||
'got: "%s".',
|
||||
is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata)
|
||||
));
|
||||
}
|
||||
|
||||
$nodeStack->push(new PropertyNode(
|
||||
$propertyMetadata->getPropertyValue($node->value),
|
||||
$node->cacheKey.':'.$propertyName,
|
||||
$propertyMetadata,
|
||||
$node->propertyPath
|
||||
? $node->propertyPath.'.'.$propertyName
|
||||
: $propertyName,
|
||||
$node->groups,
|
||||
$node->cascadedGroups
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$traversalStrategy = $node->traversalStrategy;
|
||||
|
||||
// If no specific traversal strategy was requested when this method
|
||||
// was called, use the traversal strategy of the class' metadata
|
||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
|
||||
// Keep the STOP_RECURSION flag, if it was set
|
||||
$traversalStrategy = $node->metadata->getTraversalStrategy()
|
||||
| ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
|
||||
}
|
||||
|
||||
// Traverse only if IMPLICIT or TRAVERSE
|
||||
if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If IMPLICIT, stop unless we deal with a Traversable
|
||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$node->value instanceof \Traversable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If TRAVERSE, the constructor will fail if we have no Traversable
|
||||
$nodeStack->push(new CollectionNode(
|
||||
$node->value,
|
||||
$node->propertyPath,
|
||||
$node->groups,
|
||||
$node->cascadedGroups,
|
||||
$traversalStrategy
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses a collection node.
|
||||
*
|
||||
* At first, each visitor is invoked for this node. Then, unless any
|
||||
* of the visitors aborts the traversal by returning false, the successor
|
||||
* nodes of the collection node are put on the stack:
|
||||
*
|
||||
* - for each object in the collection with associated class metadata, a
|
||||
* new class node is put on the stack;
|
||||
* - if an object has no associated class metadata, but is traversable, and
|
||||
* unless the {@link TraversalStrategy::STOP_RECURSION} flag is set for
|
||||
* collection node, a new collection node is put on the stack for that
|
||||
* object;
|
||||
* - for each array in the collection, a new collection node is put on the
|
||||
* stack.
|
||||
*
|
||||
* @param CollectionNode $node The collection node
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
* @param \SplStack $nodeStack The stack for storing the
|
||||
* successor nodes
|
||||
*
|
||||
* @see ClassNode
|
||||
* @see CollectionNode
|
||||
*/
|
||||
private function traverseCollectionNode(CollectionNode $node, ExecutionContextInterface $context, \SplStack $nodeStack)
|
||||
{
|
||||
// Visitors have two possibilities to influence the traversal:
|
||||
//
|
||||
// 1. If a visitor's visit() method returns false, the traversal is
|
||||
// skipped entirely.
|
||||
// 2. If a visitor's visit() method removes a group from the node,
|
||||
// that group will be skipped in the subtree of that node.
|
||||
|
||||
if (false === $this->visit($node, $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (0 === count($node->groups)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$traversalStrategy = $node->traversalStrategy;
|
||||
|
||||
if ($traversalStrategy & TraversalStrategy::STOP_RECURSION) {
|
||||
$traversalStrategy = TraversalStrategy::NONE;
|
||||
} else {
|
||||
$traversalStrategy = TraversalStrategy::IMPLICIT;
|
||||
}
|
||||
|
||||
foreach ($node->value as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
// Arrays are always cascaded, independent of the specified
|
||||
// traversal strategy
|
||||
// (BC with Symfony < 2.5)
|
||||
$nodeStack->push(new CollectionNode(
|
||||
$value,
|
||||
$node->propertyPath.'['.$key.']',
|
||||
$node->groups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Scalar and null values in the collection are ignored
|
||||
// (BC with Symfony < 2.5)
|
||||
if (is_object($value)) {
|
||||
$this->cascadeObject(
|
||||
$value,
|
||||
$node->propertyPath.'['.$key.']',
|
||||
$node->groups,
|
||||
$traversalStrategy,
|
||||
$nodeStack
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses a node that is neither a class nor a collection node.
|
||||
*
|
||||
* At first, each visitor is invoked for this node. Then, unless any
|
||||
* of the visitors aborts the traversal by returning false, the successor
|
||||
* nodes of the collection node are put on the stack:
|
||||
*
|
||||
* - if the node contains an object with associated class metadata, a new
|
||||
* class node is put on the stack;
|
||||
* - if the node contains a traversable object without associated class
|
||||
* metadata and traversal is enabled according to the selected traversal
|
||||
* strategy, a collection node is put on the stack;
|
||||
* - if the node contains an array, a collection node is put on the stack.
|
||||
*
|
||||
* @param Node $node The node
|
||||
* @param ExecutionContextInterface $context The current execution context
|
||||
* @param \SplStack $nodeStack The stack for storing the
|
||||
* successor nodes
|
||||
*/
|
||||
private function traverseNode(Node $node, ExecutionContextInterface $context, \SplStack $nodeStack)
|
||||
{
|
||||
// Visitors have two possibilities to influence the traversal:
|
||||
//
|
||||
// 1. If a visitor's visit() method returns false, the traversal is
|
||||
// skipped entirely.
|
||||
// 2. If a visitor's visit() method removes a group from the node,
|
||||
// that group will be skipped in the subtree of that node.
|
||||
|
||||
if (false === $this->visit($node, $context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $node->value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The "cascadedGroups" property is set by the NodeValidationVisitor when
|
||||
// traversing group sequences
|
||||
$cascadedGroups = null !== $node->cascadedGroups
|
||||
? $node->cascadedGroups
|
||||
: $node->groups;
|
||||
|
||||
if (0 === count($cascadedGroups)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cascadingStrategy = $node->metadata->getCascadingStrategy();
|
||||
$traversalStrategy = $node->traversalStrategy;
|
||||
|
||||
// If no specific traversal strategy was requested when this method
|
||||
// was called, use the traversal strategy of the node's metadata
|
||||
if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
|
||||
// Keep the STOP_RECURSION flag, if it was set
|
||||
$traversalStrategy = $node->metadata->getTraversalStrategy()
|
||||
| ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
|
||||
}
|
||||
|
||||
if (is_array($node->value)) {
|
||||
// Arrays are always traversed, independent of the specified
|
||||
// traversal strategy
|
||||
// (BC with Symfony < 2.5)
|
||||
$nodeStack->push(new CollectionNode(
|
||||
$node->value,
|
||||
$node->propertyPath,
|
||||
$cascadedGroups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cascadingStrategy & CascadingStrategy::CASCADE) {
|
||||
// If the value is a scalar, pass it anyway, because we want
|
||||
// a NoSuchMetadataException to be thrown in that case
|
||||
// (BC with Symfony < 2.5)
|
||||
$this->cascadeObject(
|
||||
$node->value,
|
||||
$node->propertyPath,
|
||||
$cascadedGroups,
|
||||
$traversalStrategy,
|
||||
$nodeStack
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Currently, the traversal strategy can only be TRAVERSE for a
|
||||
// generic node if the cascading strategy is CASCADE. Thus, traversable
|
||||
// objects will always be handled within cascadeObject() and there's
|
||||
// nothing more to do here.
|
||||
|
||||
// see GenericMetadata::addConstraint()
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the cascading logic for an object.
|
||||
*
|
||||
* If class metadata is available for the object, a class node is put on
|
||||
* the node stack. Otherwise, if the selected traversal strategy allows
|
||||
* traversal of the object, a new collection node is put on the stack.
|
||||
* Otherwise, an exception is thrown.
|
||||
*
|
||||
* @param object $object The object to cascade
|
||||
* @param string $propertyPath The current property path
|
||||
* @param string[] $groups The validated groups
|
||||
* @param integer $traversalStrategy The strategy for traversing the
|
||||
* cascaded object
|
||||
* @param \SplStack $nodeStack The stack for storing the successor
|
||||
* nodes
|
||||
*
|
||||
* @throws NoSuchMetadataException If the object has no associated metadata
|
||||
* and does not implement {@link \Traversable}
|
||||
* or if traversal is disabled via the
|
||||
* $traversalStrategy argument
|
||||
* @throws UnsupportedMetadataException If the metadata returned by the
|
||||
* metadata factory does not implement
|
||||
* {@link ClassMetadataInterface}
|
||||
*/
|
||||
private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy, \SplStack $nodeStack)
|
||||
{
|
||||
try {
|
||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||
|
||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||
throw new UnsupportedMetadataException(sprintf(
|
||||
'The metadata factory should return instances of '.
|
||||
'"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||
'got: "%s".',
|
||||
is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
|
||||
));
|
||||
}
|
||||
|
||||
$nodeStack->push(new ClassNode(
|
||||
$object,
|
||||
spl_object_hash($object),
|
||||
$classMetadata,
|
||||
$propertyPath,
|
||||
$groups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
));
|
||||
} catch (NoSuchMetadataException $e) {
|
||||
// Rethrow if not Traversable
|
||||
if (!$object instanceof \Traversable) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Rethrow unless IMPLICIT or TRAVERSE
|
||||
if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$nodeStack->push(new CollectionNode(
|
||||
$object,
|
||||
$propertyPath,
|
||||
$groups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +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\NodeVisitor;
|
||||
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
|
||||
/**
|
||||
* Base visitor with empty method stubs.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @see NodeVisitorInterface
|
||||
*/
|
||||
abstract class AbstractVisitor implements NodeVisitorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function beforeTraversal($nodes, ExecutionContextInterface $context)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function afterTraversal($nodes, ExecutionContextInterface $context)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function visit(Node $node, ExecutionContextInterface $context)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,207 +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\NodeVisitor;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\Constraints\GroupSequence;
|
||||
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Node\ClassNode;
|
||||
use Symfony\Component\Validator\Node\CollectionNode;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
use Symfony\Component\Validator\Node\PropertyNode;
|
||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
|
||||
|
||||
/**
|
||||
* Validates a node's value against the constraints defined in it's metadata.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class NodeValidationVisitor extends AbstractVisitor
|
||||
{
|
||||
/**
|
||||
* @var ConstraintValidatorFactoryInterface
|
||||
*/
|
||||
private $validatorFactory;
|
||||
|
||||
/**
|
||||
* @var NodeTraverserInterface
|
||||
*/
|
||||
private $nodeTraverser;
|
||||
|
||||
/**
|
||||
* Creates a new visitor.
|
||||
*
|
||||
* @param NodeTraverserInterface $nodeTraverser The node traverser
|
||||
* @param ConstraintValidatorFactoryInterface $validatorFactory The validator factory
|
||||
*/
|
||||
public function __construct(NodeTraverserInterface $nodeTraverser, ConstraintValidatorFactoryInterface $validatorFactory)
|
||||
{
|
||||
$this->validatorFactory = $validatorFactory;
|
||||
$this->nodeTraverser = $nodeTraverser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a node's value against the constraints defined in the node's
|
||||
* metadata.
|
||||
*
|
||||
* Objects and constraints that were validated before in the same context
|
||||
* will be skipped.
|
||||
*
|
||||
* @param Node $node The current node
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
*
|
||||
* @return Boolean Whether to traverse the successor nodes
|
||||
*/
|
||||
public function visit(Node $node, ExecutionContextInterface $context)
|
||||
{
|
||||
if ($node instanceof CollectionNode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$context->setNode($node->value, $node->metadata, $node->propertyPath);
|
||||
|
||||
// if group (=[<G1,G2>,G3,G4]) contains group sequence (=<G1,G2>)
|
||||
// then call traverse() with each entry of the group sequence and abort
|
||||
// if necessary (G1, G2)
|
||||
// finally call traverse() with remaining entries ([G3,G4]) or
|
||||
// simply continue traversal (if possible)
|
||||
|
||||
foreach ($node->groups as $key => $group) {
|
||||
$cascadedGroup = null;
|
||||
|
||||
// Even if we remove the following clause, the constraints on an
|
||||
// object won't be validated again due to the measures taken in
|
||||
// validateNodeForGroup().
|
||||
// The following shortcut, however, prevents validatedNodeForGroup()
|
||||
// from being called at all and enhances performance a bit.
|
||||
if ($node instanceof ClassNode) {
|
||||
// Use the object hash for group sequences
|
||||
$groupHash = is_object($group) ? spl_object_hash($group) : $group;
|
||||
|
||||
if ($context->isGroupValidated($node->cacheKey, $groupHash)) {
|
||||
// Skip this group when validating the successor nodes
|
||||
// (property and/or collection nodes)
|
||||
unset($node->groups[$key]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$context->markGroupAsValidated($node->cacheKey, $groupHash);
|
||||
|
||||
// Replace the "Default" group by the group sequence defined
|
||||
// for the class, if applicable
|
||||
// This is done after checking the cache, so that
|
||||
// spl_object_hash() isn't called for this sequence and
|
||||
// "Default" is used instead in the cache. This is useful
|
||||
// if the getters below return different group sequences in
|
||||
// every call.
|
||||
if (Constraint::DEFAULT_GROUP === $group) {
|
||||
if ($node->metadata->hasGroupSequence()) {
|
||||
// The group sequence is statically defined for the class
|
||||
$group = $node->metadata->getGroupSequence();
|
||||
$cascadedGroup = Constraint::DEFAULT_GROUP;
|
||||
} elseif ($node->metadata->isGroupSequenceProvider()) {
|
||||
// The group sequence is dynamically obtained from the validated
|
||||
// object
|
||||
/** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $value */
|
||||
$group = $node->value->getGroupSequence();
|
||||
$cascadedGroup = Constraint::DEFAULT_GROUP;
|
||||
|
||||
if (!$group instanceof GroupSequence) {
|
||||
$group = new GroupSequence($group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($group instanceof GroupSequence) {
|
||||
// Traverse group sequence until a violation is generated
|
||||
$this->traverseGroupSequence($node, $group, $cascadedGroup, $context);
|
||||
|
||||
// Skip the group sequence when validating successor nodes
|
||||
unset($node->groups[$key]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate normal group
|
||||
$this->validateInGroup($node, $group, $context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a node's value in each group of a group sequence.
|
||||
*
|
||||
* If any of the groups' constraints generates a violation, subsequent
|
||||
* groups are not validated anymore.
|
||||
*
|
||||
* @param Node $node The validated node
|
||||
* @param GroupSequence $groupSequence The group sequence
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
*/
|
||||
private function traverseGroupSequence(Node $node, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context)
|
||||
{
|
||||
$violationCount = count($context->getViolations());
|
||||
$cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
|
||||
|
||||
foreach ($groupSequence->groups as $groupInSequence) {
|
||||
$node = clone $node;
|
||||
$node->groups = array($groupInSequence);
|
||||
$node->cascadedGroups = $cascadedGroups;
|
||||
|
||||
$this->nodeTraverser->traverse(array($node), $context);
|
||||
|
||||
// Abort sequence validation if a violation was generated
|
||||
if (count($context->getViolations()) > $violationCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a node's value against all constraints in the given group.
|
||||
*
|
||||
* @param Node $node The validated node
|
||||
* @param string $group The group to validate
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
* @param string $objectHash The hash of the node's
|
||||
* object (if any)
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function validateInGroup(Node $node, $group, ExecutionContextInterface $context)
|
||||
{
|
||||
$context->setGroup($group);
|
||||
|
||||
foreach ($node->metadata->findConstraints($group) as $constraint) {
|
||||
// Prevent duplicate validation of constraints, in the case
|
||||
// that constraints belong to multiple validated groups
|
||||
if (null !== $node->cacheKey) {
|
||||
$constraintHash = spl_object_hash($constraint);
|
||||
|
||||
if ($context->isConstraintValidated($node->cacheKey, $constraintHash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$context->markConstraintAsValidated($node->cacheKey, $constraintHash);
|
||||
}
|
||||
|
||||
$validator = $this->validatorFactory->getInstance($constraint);
|
||||
$validator->initialize($context);
|
||||
$validator->validate($node->value, $constraint);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +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\NodeVisitor;
|
||||
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
|
||||
/**
|
||||
* A node visitor invoked by the node traverser.
|
||||
*
|
||||
* At the beginning of the traversal, the method {@link beforeTraversal()} is
|
||||
* called. For each traversed node, the method {@link visit()} is called. At
|
||||
* last, the method {@link afterTraversal()} is called when the traversal is
|
||||
* complete.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @see \Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface
|
||||
*/
|
||||
interface NodeVisitorInterface
|
||||
{
|
||||
/**
|
||||
* Called at the beginning of a traversal.
|
||||
*
|
||||
* @param Node[] $nodes A list of Node instances
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
*
|
||||
* @return Boolean Whether to continue the traversal
|
||||
*/
|
||||
public function beforeTraversal($nodes, ExecutionContextInterface $context);
|
||||
|
||||
/**
|
||||
* Called at the end of a traversal.
|
||||
*
|
||||
* @param Node[] $nodes A list of Node instances
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
*/
|
||||
public function afterTraversal($nodes, ExecutionContextInterface $context);
|
||||
|
||||
/**
|
||||
* Called for each node during a traversal.
|
||||
*
|
||||
* @param Node $node The current node
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
*
|
||||
* @return Boolean Whether to traverse the node's successor nodes
|
||||
*/
|
||||
public function visit(Node $node, ExecutionContextInterface $context);
|
||||
}
|
|
@ -1,83 +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\NodeVisitor;
|
||||
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Validator\Node\ClassNode;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
use Symfony\Component\Validator\ObjectInitializerInterface;
|
||||
|
||||
/**
|
||||
* Initializes the objects of all class nodes.
|
||||
*
|
||||
* You have to pass at least one instance of {@link ObjectInitializerInterface}
|
||||
* to the constructor of this visitor.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ObjectInitializationVisitor extends AbstractVisitor
|
||||
{
|
||||
/**
|
||||
* @var ObjectInitializerInterface[]
|
||||
*/
|
||||
private $initializers;
|
||||
|
||||
/**
|
||||
* Creates a new visitor.
|
||||
*
|
||||
* @param ObjectInitializerInterface[] $initializers The object initializers
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $initializers)
|
||||
{
|
||||
foreach ($initializers as $initializer) {
|
||||
if (!$initializer instanceof ObjectInitializerInterface) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Validator initializers must implement '.
|
||||
'"Symfony\Component\Validator\ObjectInitializerInterface". '.
|
||||
'Got: "%s"',
|
||||
is_object($initializer) ? get_class($initializer) : gettype($initializer)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// If no initializer is present, this visitor should not even be created
|
||||
if (0 === count($initializers)) {
|
||||
throw new InvalidArgumentException('Please pass at least one initializer.');
|
||||
}
|
||||
|
||||
$this->initializers = $initializers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the {@link ObjectInitializerInterface::initialize()} method for
|
||||
* the object of each class node.
|
||||
*
|
||||
* @param Node $node The current node
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
*
|
||||
* @return Boolean Always returns true
|
||||
*/
|
||||
public function visit(Node $node, ExecutionContextInterface $context)
|
||||
{
|
||||
if ($node instanceof ClassNode) {
|
||||
foreach ($this->initializers as $initializer) {
|
||||
$initializer->initialize($node->value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,31 +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\Node;
|
||||
|
||||
use Symfony\Component\Validator\Node\ClassNode;
|
||||
|
||||
/**
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class ClassNodeTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
|
||||
*/
|
||||
public function testConstructorExpectsObject()
|
||||
{
|
||||
$metadata = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataInterface');
|
||||
|
||||
new ClassNode('foobar', null, $metadata, '', array(), array());
|
||||
}
|
||||
}
|
|
@ -1,91 +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\NodeTraverser;
|
||||
|
||||
use Symfony\Component\Validator\Mapping\GenericMetadata;
|
||||
use Symfony\Component\Validator\Node\GenericNode;
|
||||
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||
use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
|
||||
|
||||
/**
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class NonRecursiveNodeTraverserTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @var FakeMetadataFactory
|
||||
*/
|
||||
private $metadataFactory;
|
||||
|
||||
/**
|
||||
* @var NonRecursiveNodeTraverser
|
||||
*/
|
||||
private $traverser;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->metadataFactory = new FakeMetadataFactory();
|
||||
$this->traverser = new NonRecursiveNodeTraverser($this->metadataFactory);
|
||||
}
|
||||
|
||||
public function testVisitorsMayPreventTraversal()
|
||||
{
|
||||
$nodes = array(new GenericNode('value', null, new GenericMetadata(), '', array('Default')));
|
||||
$context = $this->getMock('Symfony\Component\Validator\Context\ExecutionContextInterface');
|
||||
|
||||
$visitor1 = $this->getMock('Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface');
|
||||
$visitor2 = $this->getMock('Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface');
|
||||
$visitor3 = $this->getMock('Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface');
|
||||
|
||||
$visitor1->expects($this->once())
|
||||
->method('beforeTraversal')
|
||||
->with($nodes, $context);
|
||||
|
||||
// abort traversal
|
||||
$visitor2->expects($this->once())
|
||||
->method('beforeTraversal')
|
||||
->with($nodes, $context)
|
||||
->will($this->returnValue(false));
|
||||
|
||||
// never called
|
||||
$visitor3->expects($this->never())
|
||||
->method('beforeTraversal');
|
||||
|
||||
$visitor1->expects($this->never())
|
||||
->method('visit');
|
||||
$visitor2->expects($this->never())
|
||||
->method('visit');
|
||||
$visitor2->expects($this->never())
|
||||
->method('visit');
|
||||
|
||||
// called in order to clean up
|
||||
$visitor1->expects($this->once())
|
||||
->method('afterTraversal')
|
||||
->with($nodes, $context);
|
||||
|
||||
// abort traversal
|
||||
$visitor2->expects($this->once())
|
||||
->method('afterTraversal')
|
||||
->with($nodes, $context);
|
||||
|
||||
// never called, because beforeTraversal() wasn't called either
|
||||
$visitor3->expects($this->never())
|
||||
->method('afterTraversal');
|
||||
|
||||
$this->traverser->addVisitor($visitor1);
|
||||
$this->traverser->addVisitor($visitor2);
|
||||
$this->traverser->addVisitor($visitor3);
|
||||
|
||||
$this->traverser->traverse($nodes, $context);
|
||||
}
|
||||
}
|
|
@ -1,34 +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\Validator;
|
||||
|
||||
use Symfony\Component\Validator\ConstraintValidatorFactory;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextFactory;
|
||||
use Symfony\Component\Validator\DefaultTranslator;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||
use Symfony\Component\Validator\Validator\TraversingValidator;
|
||||
|
||||
class TraversingValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||
{
|
||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
$contextFactory = new ExecutionContextFactory(new DefaultTranslator());
|
||||
$validator = new TraversingValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
|
||||
$nodeTraverser->addVisitor(new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory()));
|
||||
|
||||
return $validator;
|
||||
}
|
||||
}
|
|
@ -130,7 +130,7 @@ class ValidatorBuilderTest extends \PHPUnit_Framework_TestCase
|
|||
public function testSetApiVersion25()
|
||||
{
|
||||
$this->assertSame($this->builder, $this->builder->setApiVersion(Validation::API_VERSION_2_5));
|
||||
$this->assertInstanceOf('Symfony\Component\Validator\Validator\TraversingValidator', $this->builder->getValidator());
|
||||
$this->assertInstanceOf('Symfony\Component\Validator\Validator\RecursiveValidator', $this->builder->getValidator());
|
||||
}
|
||||
|
||||
public function testSetApiVersion24And25()
|
||||
|
|
|
@ -1,242 +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\RuntimeException;
|
||||
use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
|
||||
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\CollectionNode;
|
||||
use Symfony\Component\Validator\Node\GenericNode;
|
||||
use Symfony\Component\Validator\Node\PropertyNode;
|
||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
|
||||
use Symfony\Component\Validator\Util\PropertyPath;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ContextualValidatorInterface}.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class TraversingContextualValidator implements ContextualValidatorInterface
|
||||
{
|
||||
/**
|
||||
* @var ExecutionContextInterface
|
||||
*/
|
||||
private $context;
|
||||
|
||||
/**
|
||||
* @var NodeTraverserInterface
|
||||
*/
|
||||
private $nodeTraverser;
|
||||
|
||||
/**
|
||||
* @var MetadataFactoryInterface
|
||||
*/
|
||||
private $metadataFactory;
|
||||
|
||||
/**
|
||||
* Creates a validator for the given context.
|
||||
*
|
||||
* @param ExecutionContextInterface $context The execution context
|
||||
* @param NodeTraverserInterface $nodeTraverser The node traverser
|
||||
* @param MetadataFactoryInterface $metadataFactory The factory for fetching
|
||||
* the metadata of validated
|
||||
* objects
|
||||
*/
|
||||
public function __construct(ExecutionContextInterface $context, NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$this->context = $context;
|
||||
$this->defaultPropertyPath = $context->getPropertyPath();
|
||||
$this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP);
|
||||
$this->nodeTraverser = $nodeTraverser;
|
||||
$this->metadataFactory = $metadataFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function atPath($path)
|
||||
{
|
||||
$this->defaultPropertyPath = $this->context->getPropertyPath($path);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, $constraints = null, $groups = null)
|
||||
{
|
||||
$groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
|
||||
|
||||
if (null !== $constraints) {
|
||||
if (!is_array($constraints)) {
|
||||
$constraints = array($constraints);
|
||||
}
|
||||
|
||||
$metadata = new GenericMetadata();
|
||||
$metadata->addConstraints($constraints);
|
||||
|
||||
$node = new GenericNode(
|
||||
$value,
|
||||
is_object($value) ? spl_object_hash($value) : null,
|
||||
$metadata,
|
||||
$this->defaultPropertyPath,
|
||||
$groups
|
||||
);
|
||||
} elseif (is_array($value) || $value instanceof \Traversable && !$this->metadataFactory->hasMetadataFor($value)) {
|
||||
$node = new CollectionNode(
|
||||
$value,
|
||||
$this->defaultPropertyPath,
|
||||
$groups
|
||||
);
|
||||
} elseif (is_object($value)) {
|
||||
$metadata = $this->metadataFactory->getMetadataFor($value);
|
||||
|
||||
if (!$metadata instanceof ClassMetadataInterface) {
|
||||
throw new UnsupportedMetadataException(sprintf(
|
||||
'The metadata factory should return instances of '.
|
||||
'"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
|
||||
'got: "%s".',
|
||||
is_object($metadata) ? get_class($metadata) : gettype($metadata)
|
||||
));
|
||||
}
|
||||
|
||||
$node = new ClassNode(
|
||||
$value,
|
||||
spl_object_hash($value),
|
||||
$metadata,
|
||||
$this->defaultPropertyPath,
|
||||
$groups
|
||||
);
|
||||
} else {
|
||||
throw new RuntimeException(sprintf(
|
||||
'Cannot validate values of type "%s" automatically. Please '.
|
||||
'provide a constraint.',
|
||||
gettype($value)
|
||||
));
|
||||
}
|
||||
|
||||
$this->nodeTraverser->traverse(array($node), $this->context);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateProperty($object, $propertyName, $groups = null)
|
||||
{
|
||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||
|
||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||
// Cannot be UnsupportedMetadataException because of BC with
|
||||
// Symfony < 2.5
|
||||
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;
|
||||
$cacheKey = spl_object_hash($object);
|
||||
$nodes = array();
|
||||
|
||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||
$propertyValue = $propertyMetadata->getPropertyValue($object);
|
||||
|
||||
$nodes[] = new PropertyNode(
|
||||
$propertyValue,
|
||||
$cacheKey.':'.$propertyName,
|
||||
$propertyMetadata,
|
||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||
$groups
|
||||
);
|
||||
}
|
||||
|
||||
$this->nodeTraverser->traverse($nodes, $this->context);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
|
||||
{
|
||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||
|
||||
if (!$classMetadata instanceof ClassMetadataInterface) {
|
||||
// Cannot be UnsupportedMetadataException because of BC with
|
||||
// Symfony < 2.5
|
||||
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;
|
||||
$cacheKey = spl_object_hash($object);
|
||||
$nodes = array();
|
||||
|
||||
foreach ($propertyMetadatas as $propertyMetadata) {
|
||||
$nodes[] = new PropertyNode(
|
||||
$value,
|
||||
$cacheKey.':'.$propertyName,
|
||||
$propertyMetadata,
|
||||
PropertyPath::append($this->defaultPropertyPath, $propertyName),
|
||||
$groups,
|
||||
$groups
|
||||
);
|
||||
}
|
||||
|
||||
$this->nodeTraverser->traverse($nodes, $this->context);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViolations()
|
||||
{
|
||||
return $this->context->getViolations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the given group or list of groups to an array.
|
||||
*
|
||||
* @param mixed $groups The groups to normalize
|
||||
*
|
||||
* @return array A group array
|
||||
*/
|
||||
protected function normalizeGroups($groups)
|
||||
{
|
||||
if (is_array($groups)) {
|
||||
return $groups;
|
||||
}
|
||||
|
||||
return array($groups);
|
||||
}
|
||||
}
|
|
@ -1,128 +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\Context\ExecutionContextFactoryInterface;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverserInterface;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ValidatorInterface}.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class TraversingValidator implements ValidatorInterface
|
||||
{
|
||||
/**
|
||||
* @var ExecutionContextFactoryInterface
|
||||
*/
|
||||
protected $contextFactory;
|
||||
|
||||
/**
|
||||
* @var NodeTraverserInterface
|
||||
*/
|
||||
protected $nodeTraverser;
|
||||
|
||||
/**
|
||||
* @var MetadataFactoryInterface
|
||||
*/
|
||||
protected $metadataFactory;
|
||||
|
||||
/**
|
||||
* Creates a new validator.
|
||||
*
|
||||
* @param ExecutionContextFactoryInterface $contextFactory The factory for
|
||||
* creating new contexts
|
||||
* @param NodeTraverserInterface $nodeTraverser The node traverser
|
||||
* @param MetadataFactoryInterface $metadataFactory The factory for
|
||||
* fetching the metadata
|
||||
* of validated objects
|
||||
*/
|
||||
public function __construct(ExecutionContextFactoryInterface $contextFactory, NodeTraverserInterface $nodeTraverser, MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$this->contextFactory = $contextFactory;
|
||||
$this->nodeTraverser = $nodeTraverser;
|
||||
$this->metadataFactory = $metadataFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function startContext($root = null)
|
||||
{
|
||||
return new TraversingContextualValidator(
|
||||
$this->contextFactory->createContext($this, $root),
|
||||
$this->nodeTraverser,
|
||||
$this->metadataFactory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function inContext(ExecutionContextInterface $context)
|
||||
{
|
||||
return new TraversingContextualValidator(
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, $constraints = null, $groups = null)
|
||||
{
|
||||
return $this->startContext($value)
|
||||
->validate($value, $constraints, $groups)
|
||||
->getViolations();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateProperty($object, $propertyName, $groups = null)
|
||||
{
|
||||
return $this->startContext($object)
|
||||
->validateProperty($object, $propertyName, $groups)
|
||||
->getViolations();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatePropertyValue($object, $propertyName, $value, $groups = null)
|
||||
{
|
||||
return $this->startContext($object)
|
||||
->validatePropertyValue($object, $propertyName, $value, $groups)
|
||||
->getViolations();
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
|||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\ObjectInitializationVisitor;
|
||||
use Symfony\Component\Validator\Validator\LegacyValidator;
|
||||
use Symfony\Component\Validator\Validator\RecursiveValidator;
|
||||
use Symfony\Component\Validator\Validator\TraversingValidator;
|
||||
use Symfony\Component\Validator\Validator as ValidatorV24;
|
||||
|
||||
|
@ -416,13 +417,7 @@ class ValidatorBuilder implements ValidatorBuilderInterface
|
|||
$contextFactory = new LegacyExecutionContextFactory($metadataFactory, $translator, $this->translationDomain);
|
||||
|
||||
if (Validation::API_VERSION_2_5 === $apiVersion) {
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
if (count($this->initializers) > 0) {
|
||||
$nodeTraverser->addVisitor(new ObjectInitializationVisitor($this->initializers));
|
||||
}
|
||||
$nodeTraverser->addVisitor(new NodeValidationVisitor($nodeTraverser, $validatorFactory));
|
||||
|
||||
return new TraversingValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory);
|
||||
}
|
||||
|
||||
return new LegacyValidator($contextFactory, $metadataFactory, $validatorFactory);
|
||||
|
|
Reference in New Issue