[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.
|
* 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
|
* @since 2.5
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
*/
|
*/
|
||||||
|
@ -31,19 +38,22 @@ class ClassNode extends Node
|
||||||
/**
|
/**
|
||||||
* Creates a new class node.
|
* Creates a new class node.
|
||||||
*
|
*
|
||||||
* @param object $object The validated object
|
* @param object $object The validated object
|
||||||
* @param ClassMetadataInterface $metadata The class metadata of that
|
* @param ClassMetadataInterface $metadata The class metadata of
|
||||||
* object
|
* that object
|
||||||
* @param string $propertyPath The property path leading
|
* @param string $propertyPath The property path leading
|
||||||
* to this node
|
* to this node
|
||||||
* @param string[] $groups The groups in which this
|
* @param string[] $groups The groups in which this
|
||||||
* node should be validated
|
* node should be validated
|
||||||
* @param string[]|null $cascadedGroups The groups in which
|
* @param string[]|null $cascadedGroups The groups in which
|
||||||
* cascaded objects should be
|
* cascaded objects should
|
||||||
* validated
|
* be validated
|
||||||
* @param integer $traversalStrategy
|
* @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)
|
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;
|
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
|
* @since 2.5
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
@ -33,13 +33,19 @@ class CollectionNode extends Node
|
||||||
* @param string[]|null $cascadedGroups The groups in which
|
* @param string[]|null $cascadedGroups The groups in which
|
||||||
* cascaded objects should be
|
* cascaded objects should be
|
||||||
* validated
|
* 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)
|
public function __construct($collection, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::TRAVERSE)
|
||||||
{
|
{
|
||||||
if (!is_array($collection) && !$collection instanceof \Traversable) {
|
if (!is_array($collection) && !$collection instanceof \Traversable) {
|
||||||
|
// Must throw a ConstraintDefinitionException for backwards
|
||||||
|
// compatibility reasons with Symfony < 2.5
|
||||||
throw new ConstraintDefinitionException(sprintf(
|
throw new ConstraintDefinitionException(sprintf(
|
||||||
'Traversal was enabled for "%s", but this class '.
|
'Traversal was enabled for "%s", but this class '.
|
||||||
'does not implement "\Traversable".',
|
'does not implement "\Traversable".',
|
||||||
|
|
|
@ -16,8 +16,7 @@ namespace Symfony\Component\Validator\Node;
|
||||||
* attached to it.
|
* attached to it.
|
||||||
*
|
*
|
||||||
* Together with {@link \Symfony\Component\Validator\Mapping\GenericMetadata},
|
* Together with {@link \Symfony\Component\Validator\Mapping\GenericMetadata},
|
||||||
* this node type can be used to validate a value against some given
|
* this node type can be used to validate a value against some constraints.
|
||||||
* constraints.
|
|
||||||
*
|
*
|
||||||
* @since 2.5
|
* @since 2.5
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
|
|
@ -16,7 +16,7 @@ use Symfony\Component\Validator\Mapping\MetadataInterface;
|
||||||
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node in the validated graph.
|
* A node in the validation graph.
|
||||||
*
|
*
|
||||||
* @since 2.5
|
* @since 2.5
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
@ -59,7 +59,11 @@ abstract class Node
|
||||||
public $cascadedGroups;
|
public $cascadedGroups;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The strategy used for traversing the validated value.
|
||||||
|
*
|
||||||
* @var integer
|
* @var integer
|
||||||
|
*
|
||||||
|
* @see \Symfony\Component\Validator\Mapping\TraversalStrategy
|
||||||
*/
|
*/
|
||||||
public $traversalStrategy;
|
public $traversalStrategy;
|
||||||
|
|
||||||
|
|
|
@ -19,16 +19,23 @@ use Symfony\Component\Validator\Mapping\TraversalStrategy;
|
||||||
* Represents the value of a property and its associated metadata.
|
* Represents the value of a property and its associated metadata.
|
||||||
*
|
*
|
||||||
* If the property contains an object and should be cascaded, a new
|
* If the property contains an object and should be cascaded, a new
|
||||||
* {@link ClassNode} instance will be created for that object.
|
* {@link ClassNode} instance will be created for that object:
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
*
|
*
|
||||||
* (Article:ClassNode)
|
* (Article:ClassNode)
|
||||||
* \
|
* \
|
||||||
* (author:PropertyNode)
|
* (->author:PropertyNode)
|
||||||
* \
|
* \
|
||||||
* (Author:ClassNode)
|
* (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
|
* @since 2.5
|
||||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
*/
|
*/
|
||||||
|
@ -61,6 +68,8 @@ class PropertyNode extends Node
|
||||||
* @param integer $traversalStrategy
|
* @param integer $traversalStrategy
|
||||||
*
|
*
|
||||||
* @throws UnexpectedTypeException If $object is not an object
|
* @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)
|
public function __construct($object, $value, PropertyMetadataInterface $metadata, $propertyPath, array $groups, $cascadedGroups = null, $traversalStrategy = TraversalStrategy::IMPLICIT)
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,17 +12,85 @@
|
||||||
namespace Symfony\Component\Validator\NodeTraverser;
|
namespace Symfony\Component\Validator\NodeTraverser;
|
||||||
|
|
||||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
|
use Symfony\Component\Validator\Node\Node;
|
||||||
use Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface;
|
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>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
*/
|
*/
|
||||||
interface NodeTraverserInterface
|
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);
|
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);
|
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);
|
public function traverse(array $nodes, ExecutionContextInterface $context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,10 +24,37 @@ use Symfony\Component\Validator\Node\PropertyNode;
|
||||||
use Symfony\Component\Validator\NodeVisitor\NodeVisitorInterface;
|
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>
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*
|
||||||
|
* @see NodeTraverserInterface
|
||||||
*/
|
*/
|
||||||
class NodeTraverser implements NodeTraverserInterface
|
class NonRecursiveNodeTraverser implements NodeTraverserInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var NodeVisitorInterface[]
|
* @var NodeVisitorInterface[]
|
||||||
|
@ -39,20 +66,34 @@ class NodeTraverser implements NodeTraverserInterface
|
||||||
*/
|
*/
|
||||||
private $metadataFactory;
|
private $metadataFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Boolean
|
||||||
|
*/
|
||||||
private $traversalStarted = false;
|
private $traversalStarted = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new traverser.
|
||||||
|
*
|
||||||
|
* @param MetadataFactoryInterface $metadataFactory The metadata factory
|
||||||
|
*/
|
||||||
public function __construct(MetadataFactoryInterface $metadataFactory)
|
public function __construct(MetadataFactoryInterface $metadataFactory)
|
||||||
{
|
{
|
||||||
$this->visitors = new \SplObjectStorage();
|
$this->visitors = new \SplObjectStorage();
|
||||||
$this->nodeQueue = new \SplQueue();
|
$this->nodeStack = new \SplStack();
|
||||||
$this->metadataFactory = $metadataFactory;
|
$this->metadataFactory = $metadataFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
public function addVisitor(NodeVisitorInterface $visitor)
|
public function addVisitor(NodeVisitorInterface $visitor)
|
||||||
{
|
{
|
||||||
$this->visitors->attach($visitor);
|
$this->visitors->attach($visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
public function removeVisitor(NodeVisitorInterface $visitor)
|
public function removeVisitor(NodeVisitorInterface $visitor)
|
||||||
{
|
{
|
||||||
$this->visitors->detach($visitor);
|
$this->visitors->detach($visitor);
|
||||||
|
@ -63,45 +104,67 @@ class NodeTraverser implements NodeTraverserInterface
|
||||||
*/
|
*/
|
||||||
public function traverse(array $nodes, ExecutionContextInterface $context)
|
public function traverse(array $nodes, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
|
// beforeTraversal() and afterTraversal() are only executed for the
|
||||||
|
// top-level call of traverse()
|
||||||
$isTopLevelCall = !$this->traversalStarted;
|
$isTopLevelCall = !$this->traversalStarted;
|
||||||
|
|
||||||
if ($isTopLevelCall) {
|
if ($isTopLevelCall) {
|
||||||
|
// Remember that the traversal was already started for the case of
|
||||||
|
// recursive calls to traverse()
|
||||||
$this->traversalStarted = true;
|
$this->traversalStarted = true;
|
||||||
|
|
||||||
foreach ($this->visitors as $visitor) {
|
foreach ($this->visitors as $visitor) {
|
||||||
/** @var NodeVisitorInterface $visitor */
|
|
||||||
$visitor->beforeTraversal($nodes, $context);
|
$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) {
|
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()) {
|
// Fetch nodes from the stack and traverse them until no more nodes
|
||||||
$node = $nodeQueue->dequeue();
|
// are left. Then continue with the next node in $nodes.
|
||||||
|
while (!$nodeStack->isEmpty()) {
|
||||||
|
$node = $nodeStack->pop();
|
||||||
|
|
||||||
if ($node instanceof ClassNode) {
|
if ($node instanceof ClassNode) {
|
||||||
$this->traverseClassNode($node, $nodeQueue, $context);
|
$this->traverseClassNode($node, $context, $nodeStack);
|
||||||
} elseif ($node instanceof CollectionNode) {
|
} elseif ($node instanceof CollectionNode) {
|
||||||
$this->traverseCollectionNode($node, $nodeQueue, $context);
|
$this->traverseCollectionNode($node, $context, $nodeStack);
|
||||||
} else {
|
} else {
|
||||||
$this->traverseNode($node, $nodeQueue, $context);
|
$this->traverseNode($node, $context, $nodeStack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isTopLevelCall) {
|
if ($isTopLevelCall) {
|
||||||
foreach ($this->visitors as $visitor) {
|
foreach ($this->visitors as $visitor) {
|
||||||
/** @var NodeVisitorInterface $visitor */
|
|
||||||
$visitor->afterTraversal($nodes, $context);
|
$visitor->afterTraversal($nodes, $context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Put the traverser back into its initial state
|
||||||
$this->traversalStarted = false;
|
$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)
|
private function visit(Node $node, ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
foreach ($this->visitors as $visitor) {
|
foreach ($this->visitors as $visitor) {
|
||||||
|
@ -113,87 +176,27 @@ class NodeTraverser implements NodeTraverserInterface
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function traverseNode(Node $node, \SplQueue $nodeQueue, ExecutionContextInterface $context)
|
/**
|
||||||
{
|
* Traverses a class node.
|
||||||
// Visitors have two possibilities to influence the traversal:
|
*
|
||||||
//
|
* At first, each visitor is invoked for this node. Then, unless any
|
||||||
// 1. If a visitor's visit() method returns false, the traversal is
|
* of the visitors aborts the traversal by returning false, a property
|
||||||
// skipped entirely.
|
* node is put on the node stack for each constrained property of the class.
|
||||||
// 2. If a visitor's visit() method removes a group from the node,
|
* At last, if the class is traversable and should be traversed according
|
||||||
// that group will be skipped in the subtree of that node.
|
* to the selected traversal strategy, a new collection node is put on the
|
||||||
|
* stack.
|
||||||
if (false === $this->visit($node, $context)) {
|
*
|
||||||
return;
|
* @param ClassNode $node The class node
|
||||||
}
|
* @param ExecutionContextInterface $context The current execution context
|
||||||
|
* @param \SplStack $nodeStack The stack for storing the
|
||||||
if (null === $node->value) {
|
* successor nodes
|
||||||
return;
|
*
|
||||||
}
|
* @see ClassNode
|
||||||
|
* @see PropertyNode
|
||||||
// The "cascadedGroups" property is set by the NodeValidationVisitor when
|
* @see CollectionNode
|
||||||
// traversing group sequences
|
* @see TraversalStrategy
|
||||||
$cascadedGroups = null !== $node->cascadedGroups
|
*/
|
||||||
? $node->cascadedGroups
|
private function traverseClassNode(ClassNode $node, ExecutionContextInterface $context, \SplStack $nodeStack)
|
||||||
: $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)
|
|
||||||
{
|
{
|
||||||
// Visitors have two possibilities to influence the traversal:
|
// 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->getConstrainedProperties() as $propertyName) {
|
||||||
foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
|
foreach ($node->metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
|
||||||
$nodeQueue->enqueue(new PropertyNode(
|
$nodeStack->push(new PropertyNode(
|
||||||
$node->value,
|
$node->value,
|
||||||
$propertyMetadata->getPropertyValue($node->value),
|
$propertyMetadata->getPropertyValue($node->value),
|
||||||
$propertyMetadata,
|
$propertyMetadata,
|
||||||
|
@ -246,7 +249,7 @@ class NodeTraverser implements NodeTraverserInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
// If TRAVERSE, the constructor will fail if we have no Traversable
|
// If TRAVERSE, the constructor will fail if we have no Traversable
|
||||||
$nodeQueue->enqueue(new CollectionNode(
|
$nodeStack->push(new CollectionNode(
|
||||||
$node->value,
|
$node->value,
|
||||||
$node->propertyPath,
|
$node->propertyPath,
|
||||||
$node->groups,
|
$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:
|
// Visitors have two possibilities to influence the traversal:
|
||||||
//
|
//
|
||||||
|
@ -285,7 +312,7 @@ class NodeTraverser implements NodeTraverserInterface
|
||||||
// Arrays are always cascaded, independent of the specified
|
// Arrays are always cascaded, independent of the specified
|
||||||
// traversal strategy
|
// traversal strategy
|
||||||
// (BC with Symfony < 2.5)
|
// (BC with Symfony < 2.5)
|
||||||
$nodeQueue->enqueue(new CollectionNode(
|
$nodeStack->push(new CollectionNode(
|
||||||
$value,
|
$value,
|
||||||
$node->propertyPath.'['.$key.']',
|
$node->propertyPath.'['.$key.']',
|
||||||
$node->groups,
|
$node->groups,
|
||||||
|
@ -304,13 +331,142 @@ class NodeTraverser implements NodeTraverserInterface
|
||||||
$node->propertyPath.'['.$key.']',
|
$node->propertyPath.'['.$key.']',
|
||||||
$node->groups,
|
$node->groups,
|
||||||
$traversalStrategy,
|
$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 {
|
try {
|
||||||
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
$classMetadata = $this->metadataFactory->getMetadataFor($object);
|
||||||
|
@ -319,7 +475,7 @@ class NodeTraverser implements NodeTraverserInterface
|
||||||
// error
|
// error
|
||||||
}
|
}
|
||||||
|
|
||||||
$nodeQueue->enqueue(new ClassNode(
|
$nodeStack->push(new ClassNode(
|
||||||
$object,
|
$object,
|
||||||
$classMetadata,
|
$classMetadata,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
|
@ -338,7 +494,7 @@ class NodeTraverser implements NodeTraverserInterface
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
$nodeQueue->enqueue(new CollectionNode(
|
$nodeStack->push(new CollectionNode(
|
||||||
$object,
|
$object,
|
||||||
$propertyPath,
|
$propertyPath,
|
||||||
$groups,
|
$groups,
|
|
@ -18,7 +18,7 @@ use Symfony\Component\Validator\MetadataFactoryInterface;
|
||||||
use Symfony\Component\Validator\NodeVisitor\ContextUpdateVisitor;
|
use Symfony\Component\Validator\NodeVisitor\ContextUpdateVisitor;
|
||||||
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolvingVisitor;
|
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolvingVisitor;
|
||||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
|
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||||
use Symfony\Component\Validator\Validator\LegacyValidator;
|
use Symfony\Component\Validator\Validator\LegacyValidator;
|
||||||
|
|
||||||
class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||||
|
@ -34,7 +34,7 @@ class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||||
|
|
||||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||||
{
|
{
|
||||||
$nodeTraverser = new NodeTraverser($metadataFactory);
|
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||||
$contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
$contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
||||||
$validator = new LegacyValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
$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\ContextUpdateVisitor;
|
||||||
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolvingVisitor;
|
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolvingVisitor;
|
||||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
|
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||||
use Symfony\Component\Validator\Validator\LegacyValidator;
|
use Symfony\Component\Validator\Validator\LegacyValidator;
|
||||||
|
|
||||||
class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest
|
class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest
|
||||||
|
@ -34,7 +34,7 @@ class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest
|
||||||
|
|
||||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||||
{
|
{
|
||||||
$nodeTraverser = new NodeTraverser($metadataFactory);
|
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||||
$contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
$contextFactory = new LegacyExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
||||||
$validator = new LegacyValidator($contextFactory, $nodeTraverser, $metadataFactory);
|
$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\ContextUpdateVisitor;
|
||||||
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolvingVisitor;
|
use Symfony\Component\Validator\NodeVisitor\GroupSequenceResolvingVisitor;
|
||||||
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
use Symfony\Component\Validator\NodeVisitor\NodeValidationVisitor;
|
||||||
use Symfony\Component\Validator\NodeTraverser\NodeTraverser;
|
use Symfony\Component\Validator\NodeTraverser\NonRecursiveNodeTraverser;
|
||||||
use Symfony\Component\Validator\Validator\Validator;
|
use Symfony\Component\Validator\Validator\Validator;
|
||||||
|
|
||||||
class Validator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
class Validator2Dot5ApiTest extends Abstract2Dot5ApiTest
|
||||||
{
|
{
|
||||||
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
protected function createValidator(MetadataFactoryInterface $metadataFactory)
|
||||||
{
|
{
|
||||||
$nodeTraverser = new NodeTraverser($metadataFactory);
|
$nodeTraverser = new NonRecursiveNodeTraverser($metadataFactory);
|
||||||
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
$nodeValidator = new NodeValidationVisitor($nodeTraverser, new ConstraintValidatorFactory());
|
||||||
$contextFactory = new ExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
$contextFactory = new ExecutionContextFactory($nodeValidator, new DefaultTranslator());
|
||||||
$validator = new Validator($contextFactory, $nodeTraverser, $metadataFactory);
|
$validator = new Validator($contextFactory, $nodeTraverser, $metadataFactory);
|
||||||
|
|
Reference in New Issue