diff --git a/src/Symfony/Component/Validator/Node/ClassNode.php b/src/Symfony/Component/Validator/Node/ClassNode.php index 7c82eead9a..54e22e2d97 100644 --- a/src/Symfony/Component/Validator/Node/ClassNode.php +++ b/src/Symfony/Component/Validator/Node/ClassNode.php @@ -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 */ @@ -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) { diff --git a/src/Symfony/Component/Validator/Node/CollectionNode.php b/src/Symfony/Component/Validator/Node/CollectionNode.php index 763ed63072..ddca97a5c7 100644 --- a/src/Symfony/Component/Validator/Node/CollectionNode.php +++ b/src/Symfony/Component/Validator/Node/CollectionNode.php @@ -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 @@ -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".', diff --git a/src/Symfony/Component/Validator/Node/GenericNode.php b/src/Symfony/Component/Validator/Node/GenericNode.php index 82ee9ac7fb..9c628e0a46 100644 --- a/src/Symfony/Component/Validator/Node/GenericNode.php +++ b/src/Symfony/Component/Validator/Node/GenericNode.php @@ -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 diff --git a/src/Symfony/Component/Validator/Node/Node.php b/src/Symfony/Component/Validator/Node/Node.php index 038217bf51..56c8145c45 100644 --- a/src/Symfony/Component/Validator/Node/Node.php +++ b/src/Symfony/Component/Validator/Node/Node.php @@ -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 @@ -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; diff --git a/src/Symfony/Component/Validator/Node/PropertyNode.php b/src/Symfony/Component/Validator/Node/PropertyNode.php index 14dedb3203..8934bf1d73 100644 --- a/src/Symfony/Component/Validator/Node/PropertyNode.php +++ b/src/Symfony/Component/Validator/Node/PropertyNode.php @@ -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 */ @@ -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) { diff --git a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php index 6084403d9b..37c7c8c22c 100644 --- a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php +++ b/src/Symfony/Component/Validator/NodeTraverser/NodeTraverserInterface.php @@ -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 */ 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); } diff --git a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php b/src/Symfony/Component/Validator/NodeTraverser/NonRecursiveNodeTraverser.php similarity index 52% rename from src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php rename to src/Symfony/Component/Validator/NodeTraverser/NonRecursiveNodeTraverser.php index be9f2d2b88..ca1e2a9db5 100644 --- a/src/Symfony/Component/Validator/NodeTraverser/NodeTraverser.php +++ b/src/Symfony/Component/Validator/NodeTraverser/NonRecursiveNodeTraverser.php @@ -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 + * + * @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, diff --git a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php index 7447c7ce29..5468642e7d 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php @@ -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); diff --git a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php index e7fd2a8ccb..3edd86b410 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php @@ -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); diff --git a/src/Symfony/Component/Validator/Tests/Validator/Validator2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/Validator2Dot5ApiTest.php index b49c380c98..277d641b7a 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/Validator2Dot5ApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/Validator2Dot5ApiTest.php @@ -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);