1717use Symfony \Component \Validator \Group \GroupManagerInterface ;
1818use Symfony \Component \Validator \Node \ClassNode ;
1919use Symfony \Component \Validator \Node \Node ;
20+ use Symfony \Component \Validator \Node \PropertyNode ;
2021use Symfony \Component \Validator \NodeTraverser \NodeTraverserInterface ;
2122
2223/**
@@ -27,6 +28,8 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
2728{
2829 private $ validatedObjects = array ();
2930
31+ private $ validatedConstraints = array ();
32+
3033 /**
3134 * @var ConstraintValidatorFactoryInterface
3235 */
@@ -44,10 +47,15 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
4447
4548 private $ currentGroup ;
4649
50+ private $ currentObjectHash ;
51+
52+ private $ objectHashStack ;
53+
4754 public function __construct (NodeTraverserInterface $ nodeTraverser , ConstraintValidatorFactoryInterface $ validatorFactory )
4855 {
4956 $ this ->validatorFactory = $ validatorFactory ;
5057 $ this ->nodeTraverser = $ nodeTraverser ;
58+ $ this ->objectHashStack = new \SplStack ();
5159 }
5260
5361 public function initialize (ExecutionContextManagerInterface $ contextManager )
@@ -58,39 +66,48 @@ public function initialize(ExecutionContextManagerInterface $contextManager)
5866 public function afterTraversal (array $ nodes )
5967 {
6068 $ this ->validatedObjects = array ();
69+ $ this ->validatedConstraints = array ();
70+ $ this ->objectHashStack = new \SplStack ();
6171 }
6272
6373 public function enterNode (Node $ node )
6474 {
65- $ objectHash = $ node instanceof ClassNode
66- ? spl_object_hash ($ node ->value )
67- : null ;
75+ if ($ node instanceof ClassNode) {
76+ $ objectHash = spl_object_hash ($ node ->value );
77+ $ this ->objectHashStack ->push ($ objectHash );
78+ } elseif ($ node instanceof PropertyNode) {
79+ $ objectHash = $ this ->objectHashStack ->top ();
80+ } else {
81+ $ objectHash = null ;
82+ }
6883
6984 // if group (=[<G1,G2>,G3,G4]) contains group sequence (=<G1,G2>)
7085 // then call traverse() with each entry of the group sequence and abort
7186 // if necessary (G1, G2)
7287 // finally call traverse() with remaining entries ([G3,G4]) or
7388 // simply continue traversal (if possible)
7489
75- foreach ($ node ->groups as $ group ) {
76- // Validate object nodes only once per group
77- if (null !== $ objectHash ) {
90+ foreach ($ node ->groups as $ key => $ group ) {
91+ // Remember which object was validated for which group
92+ // Skip validation if the object was already validated for this
93+ // group
94+ if ($ node instanceof ClassNode) {
7895 // Use the object hash for group sequences
7996 $ groupHash = is_object ($ group ) ? spl_object_hash ($ group ) : $ group ;
8097
81- // Exit, if the object is already validated for the current group
8298 if (isset ($ this ->validatedObjects [$ objectHash ][$ groupHash ])) {
83- return false ;
99+ // Skip this group when validating properties
100+ unset($ node ->groups [$ key ]);
101+
102+ continue ;
84103 }
85104
86- // Remember validating this object before starting and possibly
87- // traversing the object graph
88105 $ this ->validatedObjects [$ objectHash ][$ groupHash ] = true ;
89106 }
90107
91- // Validate group sequence until a violation is generated
108+ // Validate normal group
92109 if (!$ group instanceof GroupSequence) {
93- $ this ->validateNodeForGroup ($ node , $ group );
110+ $ this ->validateNodeForGroup ($ objectHash , $ node , $ group );
94111
95112 continue ;
96113 }
@@ -100,6 +117,7 @@ public function enterNode(Node $node)
100117 continue ;
101118 }
102119
120+ // Traverse group sequence until a violation is generated
103121 $ this ->traverseGroupSequence ($ node , $ group );
104122
105123 // Optimization: If the groups only contain the group sequence,
@@ -139,17 +157,31 @@ private function traverseGroupSequence(ClassNode $node, GroupSequence $groupSequ
139157 }
140158
141159 /**
160+ * @param $objectHash
142161 * @param Node $node
143162 * @param $group
144163 *
145164 * @throws \Exception
146165 */
147- private function validateNodeForGroup (Node $ node , $ group )
166+ private function validateNodeForGroup ($ objectHash , Node $ node , $ group )
148167 {
149168 try {
150169 $ this ->currentGroup = $ group ;
151170
152171 foreach ($ node ->metadata ->findConstraints ($ group ) as $ constraint ) {
172+ // Remember the validated constraints of each object to prevent
173+ // duplicate validation of constraints that belong to multiple
174+ // validated groups
175+ if (null !== $ objectHash ) {
176+ $ constraintHash = spl_object_hash ($ constraint );
177+
178+ if (isset ($ this ->validatedConstraints [$ objectHash ][$ constraintHash ])) {
179+ continue ;
180+ }
181+
182+ $ this ->validatedConstraints [$ objectHash ][$ constraintHash ] = true ;
183+ }
184+
153185 $ validator = $ this ->validatorFactory ->getInstance ($ constraint );
154186 $ validator ->initialize ($ this ->contextManager ->getCurrentContext ());
155187 $ validator ->validate ($ node ->value , $ constraint );
0 commit comments