|
1 <?php |
|
2 |
|
3 /* |
|
4 * This file is part of the Symfony package. |
|
5 * |
|
6 * (c) Fabien Potencier <fabien@symfony.com> |
|
7 * |
|
8 * For the full copyright and license information, please view the LICENSE |
|
9 * file that was distributed with this source code. |
|
10 */ |
|
11 |
|
12 namespace Symfony\Component\Validator; |
|
13 |
|
14 use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; |
|
15 use Symfony\Component\Validator\Constraint; |
|
16 use Symfony\Component\Validator\Constraints\All; |
|
17 use Symfony\Component\Validator\Constraints\Valid; |
|
18 use Symfony\Component\Validator\Exception\UnexpectedTypeException; |
|
19 use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface; |
|
20 use Symfony\Component\Validator\Mapping\ClassMetadata; |
|
21 use Symfony\Component\Validator\Mapping\MemberMetadata; |
|
22 |
|
23 /** |
|
24 * Responsible for walking over and initializing validation on different |
|
25 * types of items. |
|
26 * |
|
27 * @author Fabien Potencier <fabien@symfony.com> |
|
28 * @author Bernhard Schussek <bernhard.schussek@symfony.com> |
|
29 */ |
|
30 class GraphWalker |
|
31 { |
|
32 protected $context; |
|
33 protected $validatorFactory; |
|
34 protected $metadataFactory; |
|
35 protected $validatorInitializers = array(); |
|
36 protected $validatedObjects = array(); |
|
37 |
|
38 public function __construct($root, ClassMetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $factory, array $validatorInitializers = array()) |
|
39 { |
|
40 $this->context = new ExecutionContext($root, $this, $metadataFactory); |
|
41 $this->validatorFactory = $factory; |
|
42 $this->metadataFactory = $metadataFactory; |
|
43 $this->validatorInitializers = $validatorInitializers; |
|
44 } |
|
45 |
|
46 /** |
|
47 * @return ConstraintViolationList |
|
48 */ |
|
49 public function getViolations() |
|
50 { |
|
51 return $this->context->getViolations(); |
|
52 } |
|
53 |
|
54 /** |
|
55 * Initialize validation on the given object using the given metadata |
|
56 * instance and validation group. |
|
57 * |
|
58 * @param ClassMetadata $metadata |
|
59 * @param object $object The object to validate |
|
60 * @param string $group The validator group to use for validation |
|
61 * @param string $propertyPath |
|
62 */ |
|
63 public function walkObject(ClassMetadata $metadata, $object, $group, $propertyPath) |
|
64 { |
|
65 foreach ($this->validatorInitializers as $initializer) { |
|
66 if (!$initializer instanceof ObjectInitializerInterface) { |
|
67 throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.'); |
|
68 } |
|
69 $initializer->initialize($object); |
|
70 } |
|
71 |
|
72 $this->context->setCurrentClass($metadata->getClassName()); |
|
73 |
|
74 if ($group === Constraint::DEFAULT_GROUP && $metadata->hasGroupSequence()) { |
|
75 $groups = $metadata->getGroupSequence(); |
|
76 foreach ($groups as $group) { |
|
77 $this->walkObjectForGroup($metadata, $object, $group, $propertyPath, Constraint::DEFAULT_GROUP); |
|
78 |
|
79 if (count($this->getViolations()) > 0) { |
|
80 break; |
|
81 } |
|
82 } |
|
83 } else { |
|
84 $this->walkObjectForGroup($metadata, $object, $group, $propertyPath); |
|
85 } |
|
86 } |
|
87 |
|
88 protected function walkObjectForGroup(ClassMetadata $metadata, $object, $group, $propertyPath, $propagatedGroup = null) |
|
89 { |
|
90 $hash = spl_object_hash($object); |
|
91 |
|
92 // Exit, if the object is already validated for the current group |
|
93 if (isset($this->validatedObjects[$hash])) { |
|
94 if (isset($this->validatedObjects[$hash][$group])) { |
|
95 return; |
|
96 } |
|
97 } else { |
|
98 $this->validatedObjects[$hash] = array(); |
|
99 } |
|
100 |
|
101 // Remember validating this object before starting and possibly |
|
102 // traversing the object graph |
|
103 $this->validatedObjects[$hash][$group] = true; |
|
104 |
|
105 foreach ($metadata->findConstraints($group) as $constraint) { |
|
106 $this->walkConstraint($constraint, $object, $group, $propertyPath); |
|
107 } |
|
108 |
|
109 if (null !== $object) { |
|
110 foreach ($metadata->getConstrainedProperties() as $property) { |
|
111 $localPropertyPath = empty($propertyPath) ? $property : $propertyPath.'.'.$property; |
|
112 |
|
113 $this->walkProperty($metadata, $property, $object, $group, $localPropertyPath, $propagatedGroup); |
|
114 } |
|
115 } |
|
116 } |
|
117 |
|
118 public function walkProperty(ClassMetadata $metadata, $property, $object, $group, $propertyPath, $propagatedGroup = null) |
|
119 { |
|
120 foreach ($metadata->getMemberMetadatas($property) as $member) { |
|
121 $this->walkMember($member, $member->getValue($object), $group, $propertyPath, $propagatedGroup); |
|
122 } |
|
123 } |
|
124 |
|
125 public function walkPropertyValue(ClassMetadata $metadata, $property, $value, $group, $propertyPath) |
|
126 { |
|
127 foreach ($metadata->getMemberMetadatas($property) as $member) { |
|
128 $this->walkMember($member, $value, $group, $propertyPath); |
|
129 } |
|
130 } |
|
131 |
|
132 protected function walkMember(MemberMetadata $metadata, $value, $group, $propertyPath, $propagatedGroup = null) |
|
133 { |
|
134 $this->context->setCurrentProperty($metadata->getPropertyName()); |
|
135 |
|
136 foreach ($metadata->findConstraints($group) as $constraint) { |
|
137 $this->walkConstraint($constraint, $value, $group, $propertyPath); |
|
138 } |
|
139 |
|
140 if ($metadata->isCascaded()) { |
|
141 $this->walkReference($value, $propagatedGroup ?: $group, $propertyPath, $metadata->isCollectionCascaded()); |
|
142 } |
|
143 } |
|
144 |
|
145 public function walkReference($value, $group, $propertyPath, $traverse) |
|
146 { |
|
147 if (null !== $value) { |
|
148 if (!is_object($value) && !is_array($value)) { |
|
149 throw new UnexpectedTypeException($value, 'object or array'); |
|
150 } |
|
151 |
|
152 if ($traverse && (is_array($value) || $value instanceof \Traversable)) { |
|
153 foreach ($value as $key => $element) { |
|
154 // Ignore any scalar values in the collection |
|
155 if (is_object($element) || is_array($element)) { |
|
156 $this->walkReference($element, $group, $propertyPath.'['.$key.']', $traverse); |
|
157 } |
|
158 } |
|
159 } |
|
160 |
|
161 if (is_object($value)) { |
|
162 $metadata = $this->metadataFactory->getClassMetadata(get_class($value)); |
|
163 $this->walkObject($metadata, $value, $group, $propertyPath); |
|
164 } |
|
165 } |
|
166 } |
|
167 |
|
168 public function walkConstraint(Constraint $constraint, $value, $group, $propertyPath) |
|
169 { |
|
170 $validator = $this->validatorFactory->getInstance($constraint); |
|
171 |
|
172 $this->context->setPropertyPath($propertyPath); |
|
173 $this->context->setGroup($group); |
|
174 |
|
175 $validator->initialize($this->context); |
|
176 |
|
177 if (!$validator->isValid($value, $constraint)) { |
|
178 // Resetting the property path. This is needed because some |
|
179 // validators, like CollectionValidator, use the walker internally |
|
180 // and so change the context. |
|
181 $this->context->setPropertyPath($propertyPath); |
|
182 |
|
183 $this->context->addViolation( |
|
184 $validator->getMessageTemplate(), |
|
185 $validator->getMessageParameters(), |
|
186 $value |
|
187 ); |
|
188 } |
|
189 } |
|
190 } |