[Validator] Completed inline documentation of the Node classes and the NodeTraverser
This commit is contained in:
parent
dbce5a2f6a
commit
822fe4712f
|
@ -18,6 +18,13 @@ 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>
|
||||
*/
|
||||
|
@ -31,19 +38,22 @@ class ClassNode extends Node
|
|||
/**
|
||||
* 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
|
||||
* @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 \Symfony\Component\Validator\Exception\UnexpectedTypeException
|
||||
* @throws UnexpectedTypeException If $object is not an object
|
||||
*
|
||||
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||
*/
|
||||
public function __construct($object, ClassMetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@ use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
|
|||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
|
||||
/**
|
||||
* Represents an traversable collection in the validation graph.
|
||||
* Represents a traversable value in the validation graph.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
|
@ -33,13 +33,19 @@ class CollectionNode extends Node
|
|||
* @param string[]|null $cascadedGroups The groups in which
|
||||
* cascaded objects should be
|
||||
* validated
|
||||
* @param integer $traversalStrategy The traversal strategy
|
||||
* @param integer $traversalStrategy The strategy used for
|
||||
* traversing the collection
|
||||
*
|
||||
* @throws \Symfony\Component\Validator\Exception\ConstraintDefinitionException
|
||||
* @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".',
|
||||
|
|
|
@ -16,8 +16,7 @@ namespace Symfony\Component\Validator\Node;
|
|||
* attached to it.
|
||||
*
|
||||
* Together with {@link \Symfony\Component\Validator\Mapping\GenericMetadata},
|
||||
* this node type can be used to validate a value against some given
|
||||
* constraints.
|
||||
* this node type can be used to validate a value against some constraints.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
|
|
|
@ -16,7 +16,7 @@ use Symfony\Component\Validator\Mapping\MetadataInterface;
|
|||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||
|
||||
/**
|
||||
* A node in the validated graph.
|
||||
* A node in the validation graph.
|
||||
*
|
||||
* @since 2.5
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
|
@ -59,7 +59,11 @@ abstract class Node
|
|||
public $cascadedGroups;
|
||||
|
||||
/**
|
||||
* The strategy used for traversing the validated value.
|
||||
*
|
||||
* @var integer
|
||||
*
|
||||
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||
*/
|
||||
public $traversalStrategy;
|
||||
|
||||
|
|
|
@ -19,16 +19,23 @@ 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.
|
||||
*
|
||||
* Example:
|
||||
* {@link ClassNode} instance will be created for that object:
|
||||
*
|
||||
* (Article:ClassNode)
|
||||
* \
|
||||
* (author:PropertyNode)
|
||||
* (->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>
|
||||
*/
|
||||
|
@ -61,6 +68,8 @@ class PropertyNode extends Node
|
|||
* @param integer $traversalStrategy
|
||||
*
|
||||
* @throws UnexpectedTypeException If $object is not an object
|
||||
*
|
||||
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||
*/
|
||||
public function __construct($object, $value, PropertyMetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||
{
|
||||
|
|
|
@ -12,17 +12,85 @@
|
|||
namespace Symfony\Component\Validator\NodeTraverser;
|
||||
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Validator\Node\Node;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface;
|
||||
|
||||
/**
|
||||
* @since %%NextVersion%%
|
||||
* 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 should be called in the same order in which they are added to
|
||||
* the traverser.
|
||||
*
|
||||
* 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(array $nodes, ExecutionContextInterface $context);
|
||||
}
|
||||
|
|
|
@ -24,10 +24,37 @@ use Symfony\Component\Validator\Node\PropertyNode;
|
|||
use Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface;
|
||||
|
||||
/**
|
||||
* @since %%NextVersion%%
|
||||
* 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
|
||||
*/
|
||||
class NodeTraverser implements NodeTraverserInterface
|
||||
class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
||||
{
|
||||
/**
|
||||
* @var NodeVisitorInterface[]
|
||||
|
@ -39,20 +66,34 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
*/
|
||||
private $metadataFactory;
|
||||
|
||||
/**
|
||||
* @var Boolean
|
||||
*/
|
||||
private $traversalStarted = false;
|
||||
|
||||
/**
|
||||
* Creates a new traverser.
|
||||
*
|
||||
* @param MetadataFactoryInterface $metadataFactory The metadata factory
|
||||
*/
|
||||
public function __construct(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$this->visitors = new \SplObjectStorage();
|
||||
$this->nodeQueue = new \SplQueue();
|
||||
$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);
|
||||
|
@ -63,45 +104,67 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
*/
|
||||
public function traverse(array $nodes, ExecutionContextInterface $context)
|
||||
{
|
||||
// beforeTraversal() and afterTraversal() are only executed for the
|
||||
// top-level call of traverse()
|
||||
$isTopLevelCall = !$this->traversalStarted;
|
||||
|
||||
if ($isTopLevelCall) {
|
||||
// Remember that the traversal was already started for the case of
|
||||
// recursive calls to traverse()
|
||||
$this->traversalStarted = true;
|
||||
|
||||
foreach ($this->visitors as $visitor) {
|
||||
/** @var NodeVisitorInterface $visitor */
|
||||
$visitor->beforeTraversal($nodes, $context);
|
||||
}
|
||||
}
|
||||
|
||||
$nodeQueue = new \SplQueue();
|
||||
// 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) {
|
||||
$nodeQueue->enqueue($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);
|
||||
|
||||
while (!$nodeQueue->isEmpty()) {
|
||||
$node = $nodeQueue->dequeue();
|
||||
// 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, $nodeQueue, $context);
|
||||
$this->traverseClassNode($node, $context, $nodeStack);
|
||||
} elseif ($node instanceof CollectionNode) {
|
||||
$this->traverseCollectionNode($node, $nodeQueue, $context);
|
||||
$this->traverseCollectionNode($node, $context, $nodeStack);
|
||||
} else {
|
||||
$this->traverseNode($node, $nodeQueue, $context);
|
||||
$this->traverseNode($node, $context, $nodeStack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isTopLevelCall) {
|
||||
foreach ($this->visitors as $visitor) {
|
||||
/** @var NodeVisitorInterface $visitor */
|
||||
$visitor->afterTraversal($nodes, $context);
|
||||
}
|
||||
|
||||
// Put the traverser back into its initial state
|
||||
$this->traversalStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
@ -113,87 +176,27 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
return true;
|
||||
}
|
||||
|
||||
private function traverseNode(Node $node, \SplQueue $nodeQueue, ExecutionContextInterface $context)
|
||||
{
|
||||
// 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->metadata->getTraversalStrategy();
|
||||
|
||||
if (is_array($node->value)) {
|
||||
// Arrays are always traversed, independent of the specified
|
||||
// traversal strategy
|
||||
// (BC with Symfony < 2.5)
|
||||
$nodeQueue->enqueue(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,
|
||||
$nodeQueue
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 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
|
||||
$nodeQueue->enqueue(new CollectionNode(
|
||||
$node->value,
|
||||
$node->propertyPath,
|
||||
$cascadedGroups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
));
|
||||
}
|
||||
|
||||
private function traverseClassNode(ClassNode $node, \SplQueue $nodeQueue, ExecutionContextInterface $context)
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @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:
|
||||
//
|
||||
|
@ -212,7 +215,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
|
||||
foreach ($node->metadata->getConstrainedProperties() as $propertyName) {
|
||||
foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
|
||||
$nodeQueue->enqueue(new PropertyNode(
|
||||
$nodeStack->push(new PropertyNode(
|
||||
$node->value,
|
||||
$propertyMetadata->getPropertyValue($node->value),
|
||||
$propertyMetadata,
|
||||
|
@ -246,7 +249,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
}
|
||||
|
||||
// If TRAVERSE, the constructor will fail if we have no Traversable
|
||||
$nodeQueue->enqueue(new CollectionNode(
|
||||
$nodeStack->push(new CollectionNode(
|
||||
$node->value,
|
||||
$node->propertyPath,
|
||||
$node->groups,
|
||||
|
@ -255,7 +258,31 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
));
|
||||
}
|
||||
|
||||
private function traverseCollectionNode(CollectionNode $node, \SplQueue $nodeQueue, ExecutionContextInterface $context)
|
||||
/**
|
||||
* 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:
|
||||
//
|
||||
|
@ -285,7 +312,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
// Arrays are always cascaded, independent of the specified
|
||||
// traversal strategy
|
||||
// (BC with Symfony < 2.5)
|
||||
$nodeQueue->enqueue(new CollectionNode(
|
||||
$nodeStack->push(new CollectionNode(
|
||||
$value,
|
||||
$node->propertyPath.'['.$key.']',
|
||||
$node->groups,
|
||||
|
@ -304,13 +331,142 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
$node->propertyPath.'['.$key.']',
|
||||
$node->groups,
|
||||
$traversalStrategy,
|
||||
$nodeQueue
|
||||
$nodeStack
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy, \SplQueue $nodeQueue)
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// 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,
|
||||
$cascadedGroups,
|
||||
null,
|
||||
$traversalStrategy
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
private function cascadeObject($object, $propertyPath, array $groups, $traversalStrategy, \SplStack $nodeStack)
|
||||
{
|
||||
try {
|
||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||
|
@ -319,7 +475,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
// error
|
||||
}
|
||||
|
||||
$nodeQueue->enqueue(new ClassNode(
|
||||
$nodeStack->push(new ClassNode(
|
||||
$object,
|
||||
$classMetadata,
|
||||
$propertyPath,
|
||||
|
@ -338,7 +494,7 @@ class NodeTraverser implements NodeTraverserInterface
|
|||
throw $e;
|
||||
}
|
||||
|
||||
$nodeQueue->enqueue(new CollectionNode(
|
||||
$nodeStack->push(new CollectionNode(
|
||||
$object,
|
||||
$propertyPath,
|
||||
$groups,
|
|
@ -18,7 +18,7 @@ use Symfony\Component\Validator\MetadataFactoryInterface;
|
|||
use Symfony\Component\Validator\NodeVisitor\ContextUpdateVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolvingVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
|
||||
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||
use Symfony\Component\Validator\Validator\LegacyValidator;
|
||||
|
||||
class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||
|
@ -34,7 +34,7 @@ class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
|||
|
||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$nodeTraverser = new NodeTraverser($metadataFactory);
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||
$contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
||||
$validator = new LegacyValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
|
|
|
@ -18,7 +18,7 @@ use Symfony\Component\Validator\MetadataFactoryInterface;
|
|||
use Symfony\Component\Validator\NodeVisitor\ContextUpdateVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolvingVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
|
||||
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||
use Symfony\Component\Validator\Validator\LegacyValidator;
|
||||
|
||||
class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest
|
||||
|
@ -34,7 +34,7 @@ class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest
|
|||
|
||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$nodeTraverser = new NodeTraverser($metadataFactory);
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||
$contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
||||
$validator = new LegacyValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
|
|
|
@ -18,14 +18,14 @@ use Symfony\Component\Validator\MetadataFactoryInterface;
|
|||
use Symfony\Component\Validator\NodeVisitor\ContextUpdateVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolvingVisitor;
|
||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
|
||||
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||
use Symfony\Component\Validator\Validator\Validator;
|
||||
|
||||
class Validator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||
{
|
||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||
{
|
||||
$nodeTraverser = new NodeTraverser($metadataFactory);
|
||||
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||
$contextFactory = new ExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
||||
$validator = new Validator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||
|
|
Reference in New Issue