[BUGFIX] Keep existing validation errors for recursive domain relations
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Validation / Validator / GenericObjectValidator.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Validation\Validator;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
18
19 /**
20 * A generic object validator which allows for specifying property validators
21 */
22 class GenericObjectValidator extends AbstractValidator implements ObjectValidatorInterface
23 {
24 /**
25 * @var \SplObjectStorage[]
26 */
27 protected $propertyValidators = [];
28
29 /**
30 * Checks if the given value is valid according to the validator, and returns
31 * the Error Messages object which occurred.
32 *
33 * @param mixed $value The value that should be validated
34 * @return \TYPO3\CMS\Extbase\Error\Result
35 * @api
36 */
37 public function validate($value)
38 {
39 if (is_object($value) && $this->isValidatedAlready($value)) {
40 return $this->result;
41 }
42
43 $this->result = new \TYPO3\CMS\Extbase\Error\Result();
44 if ($this->acceptsEmptyValues === false || $this->isEmpty($value) === false) {
45 if (!is_object($value)) {
46 $this->addError('Object expected, %1$s given.', 1241099149, [gettype($value)]);
47 } elseif ($this->isValidatedAlready($value) === false) {
48 $this->markInstanceAsValidated($value);
49 $this->isValid($value);
50 }
51 }
52
53 return $this->result;
54 }
55
56 /**
57 * Load the property value to be used for validation.
58 *
59 * In case the object is a doctrine proxy, we need to load the real instance first.
60 *
61 * @param object $object
62 * @param string $propertyName
63 * @return mixed
64 */
65 protected function getPropertyValue($object, $propertyName)
66 {
67 // @todo add support for lazy loading proxies, if needed
68 if (ObjectAccess::isPropertyGettable($object, $propertyName)) {
69 return ObjectAccess::getProperty($object, $propertyName);
70 }
71 return ObjectAccess::getProperty($object, $propertyName, true);
72 }
73
74 /**
75 * Checks if the specified property of the given object is valid, and adds
76 * found errors to the $messages object.
77 *
78 * @param mixed $value The value to be validated
79 * @param \Traversable $validators The validators to be called on the value
80 * @param string $propertyName Name of ther property to check
81 */
82 protected function checkProperty($value, $validators, $propertyName)
83 {
84 /** @var \TYPO3\CMS\Extbase\Error\Result $result */
85 $result = null;
86 foreach ($validators as $validator) {
87 if ($validator instanceof ObjectValidatorInterface) {
88 $validator->setValidatedInstancesContainer($this->validatedInstancesContainer);
89 }
90 $currentResult = $validator->validate($value);
91 if ($currentResult->hasMessages()) {
92 if ($result == null) {
93 $result = $currentResult;
94 } else {
95 $result->merge($currentResult);
96 }
97 }
98 }
99 if ($result != null) {
100 $this->result->forProperty($propertyName)->merge($result);
101 }
102 }
103
104 /**
105 * Checks if the given value is valid according to the property validators.
106 *
107 * @param mixed $object The value that should be validated
108 * @api
109 */
110 protected function isValid($object)
111 {
112 foreach ($this->propertyValidators as $propertyName => $validators) {
113 $propertyValue = $this->getPropertyValue($object, $propertyName);
114 $this->checkProperty($propertyValue, $validators, $propertyName);
115 }
116 }
117
118 /**
119 * Checks the given object can be validated by the validator implementation
120 *
121 * @param mixed $object The object to be checked
122 * @return bool TRUE if the given value is an object
123 * @api
124 */
125 public function canValidate($object)
126 {
127 return is_object($object);
128 }
129
130 /**
131 * Adds the given validator for validation of the specified property.
132 *
133 * @param string $propertyName Name of the property to validate
134 * @param ValidatorInterface $validator The property validator
135 * @api
136 */
137 public function addPropertyValidator($propertyName, ValidatorInterface $validator)
138 {
139 if (!isset($this->propertyValidators[$propertyName])) {
140 $this->propertyValidators[$propertyName] = new \SplObjectStorage();
141 }
142 $this->propertyValidators[$propertyName]->attach($validator);
143 }
144
145 /**
146 * @param object $object
147 * @return bool
148 */
149 protected function isValidatedAlready($object)
150 {
151 if ($this->validatedInstancesContainer === null) {
152 $this->validatedInstancesContainer = new \SplObjectStorage();
153 }
154 if ($this->validatedInstancesContainer->contains($object)) {
155 return true;
156 }
157
158 return false;
159 }
160
161 /**
162 * @param $object
163 */
164 protected function markInstanceAsValidated($object)
165 {
166 $this->validatedInstancesContainer->attach($object);
167 }
168
169 /**
170 * Returns all property validators - or only validators of the specified property
171 *
172 * @param string $propertyName Name of the property to return validators for
173 * @return array An array of validators
174 */
175 public function getPropertyValidators($propertyName = null)
176 {
177 if ($propertyName !== null) {
178 return (isset($this->propertyValidators[$propertyName])) ? $this->propertyValidators[$propertyName] : [];
179 }
180 return $this->propertyValidators;
181 }
182
183 /**
184 * @var \SplObjectStorage
185 */
186 protected $validatedInstancesContainer;
187
188 /**
189 * Allows to set a container to keep track of validated instances.
190 *
191 * @param \SplObjectStorage $validatedInstancesContainer A container to keep track of validated instances
192 * @api
193 */
194 public function setValidatedInstancesContainer(\SplObjectStorage $validatedInstancesContainer)
195 {
196 $this->validatedInstancesContainer = $validatedInstancesContainer;
197 }
198 }