17
17
use Symfony \Component \Validator \Group \GroupManagerInterface ;
18
18
use Symfony \Component \Validator \Node \ClassNode ;
19
19
use Symfony \Component \Validator \Node \Node ;
20
+ use Symfony \Component \Validator \Node \PropertyNode ;
20
21
use Symfony \Component \Validator \NodeTraverser \NodeTraverserInterface ;
21
22
22
23
/**
@@ -27,6 +28,8 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
27
28
{
28
29
private $ validatedObjects = array ();
29
30
31
+ private $ validatedConstraints = array ();
32
+
30
33
/**
31
34
* @var ConstraintValidatorFactoryInterface
32
35
*/
@@ -44,10 +47,15 @@ class NodeValidator extends AbstractVisitor implements GroupManagerInterface
44
47
45
48
private $ currentGroup ;
46
49
50
+ private $ currentObjectHash ;
51
+
52
+ private $ objectHashStack ;
53
+
47
54
public function __construct (NodeTraverserInterface $ nodeTraverser , ConstraintValidatorFactoryInterface $ validatorFactory )
48
55
{
49
56
$ this ->validatorFactory = $ validatorFactory ;
50
57
$ this ->nodeTraverser = $ nodeTraverser ;
58
+ $ this ->objectHashStack = new \SplStack ();
51
59
}
52
60
53
61
public function initialize (ExecutionContextManagerInterface $ contextManager )
@@ -58,39 +66,48 @@ public function initialize(ExecutionContextManagerInterface $contextManager)
58
66
public function afterTraversal (array $ nodes )
59
67
{
60
68
$ this ->validatedObjects = array ();
69
+ $ this ->validatedConstraints = array ();
70
+ $ this ->objectHashStack = new \SplStack ();
61
71
}
62
72
63
73
public function enterNode(Node $ node )
64
74
{
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
+ }
68
83
69
84
// if group (=[<G1,G2>,G3,G4]) contains group sequence (=<G1,G2>)
70
85
// then call traverse() with each entry of the group sequence and abort
71
86
// if necessary (G1, G2)
72
87
// finally call traverse() with remaining entries ([G3,G4]) or
73
88
// simply continue traversal (if possible)
74
89
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) {
78
95
// Use the object hash for group sequences
79
96
$ groupHash = is_object ($ group ) ? spl_object_hash ($ group ) : $ group ;
80
97
81
- // Exit, if the object is already validated for the current group
82
98
if (isset ($ this ->validatedObjects [$ objectHash ][$ groupHash ])) {
83
- return false ;
99
+ // Skip this group when validating properties
100
+ unset($ node ->groups [$ key ]);
101
+
102
+ continue ;
84
103
}
85
104
86
- // Remember validating this object before starting and possibly
87
- // traversing the object graph
88
105
$ this ->validatedObjects [$ objectHash ][$ groupHash ] = true ;
89
106
}
90
107
91
- // Validate group sequence until a violation is generated
108
+ // Validate normal group
92
109
if (!$ group instanceof GroupSequence) {
93
- $ this ->validateNodeForGroup ($ node , $ group );
110
+ $ this ->validateNodeForGroup ($ objectHash , $ node , $ group );
94
111
95
112
continue ;
96
113
}
@@ -100,6 +117,7 @@ public function enterNode(Node $node)
100
117
continue ;
101
118
}
102
119
120
+ // Traverse group sequence until a violation is generated
103
121
$ this ->traverseGroupSequence ($ node , $ group );
104
122
105
123
// Optimization: If the groups only contain the group sequence,
@@ -139,17 +157,31 @@ private function traverseGroupSequence(ClassNode $node, GroupSequence $groupSequ
139
157
}
140
158
141
159
/**
160
+ * @param $objectHash
142
161
* @param Node $node
143
162
* @param $group
144
163
*
145
164
* @throws \Exception
146
165
*/
147
- private function validateNodeForGroup (Node $ node , $ group )
166
+ private function validateNodeForGroup ($ objectHash , Node $ node , $ group )
148
167
{
149
168
try {
150
169
$ this ->currentGroup = $ group ;
151
170
152
171
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
+
153
185
$ validator = $ this ->validatorFactory ->getInstance ($ constraint );
154
186
$ validator ->initialize ($ this ->contextManager ->getCurrentContext ());
155
187
$ validator ->validate ($ node ->value , $ constraint );
0 commit comments