1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* 2017 Romain CANON <[email protected]> |
4
|
|
|
* |
5
|
|
|
* This file is part of the TYPO3 Configuration Object project. |
6
|
|
|
* It is free software; you can redistribute it and/or modify it |
7
|
|
|
* under the terms of the GNU General Public License, either |
8
|
|
|
* version 3 of the License, or any later version. |
9
|
|
|
* |
10
|
|
|
* For the full copyright and license information, see: |
11
|
|
|
* http://www.gnu.org/licenses/gpl-3.0.html |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace Romm\ConfigurationObject\Validation; |
15
|
|
|
|
16
|
|
|
use Romm\ConfigurationObject\Core\Core; |
17
|
|
|
use Romm\ConfigurationObject\Core\Service\ObjectService; |
18
|
|
|
use Romm\ConfigurationObject\Service\Items\MixedTypes\MixedTypesInterface; |
19
|
|
|
use Romm\ConfigurationObject\Service\Items\MixedTypes\MixedTypesService; |
20
|
|
|
use Romm\ConfigurationObject\Validation\Validator\Internal\ConfigurationObjectValidator; |
21
|
|
|
use Romm\ConfigurationObject\Validation\Validator\Internal\MixedTypeCollectionValidator; |
22
|
|
|
use Romm\ConfigurationObject\Validation\Validator\Internal\MixedTypeValidator; |
23
|
|
|
use TYPO3\CMS\Extbase\Reflection\ReflectionService as ExtbaseReflectionService; |
24
|
|
|
use TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator; |
25
|
|
|
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator; |
26
|
|
|
use TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator; |
27
|
|
|
use TYPO3\CMS\Extbase\Validation\Validator\ObjectValidatorInterface; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Customized validator resolver, which it mostly used to support the mixed |
31
|
|
|
* types. |
32
|
|
|
* |
33
|
|
|
* When an instance of validator is created, we check if the type of this |
34
|
|
|
* validator is `CollectionValidator`: in this case we use a custom one instead: |
35
|
|
|
* `MixedTypeCollectionValidator` which will support the mixed types feature. |
36
|
|
|
*/ |
37
|
|
|
class ValidatorResolver extends \TYPO3\CMS\Extbase\Validation\ValidatorResolver |
38
|
|
|
{ |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var array |
42
|
|
|
*/ |
43
|
|
|
protected $baseValidatorConjunctionsWithChecks = []; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var ObjectService |
47
|
|
|
*/ |
48
|
|
|
protected $objectService; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @inheritdoc |
52
|
|
|
*/ |
53
|
|
|
public function createValidator($validatorType, array $validatorOptions = []) |
54
|
|
|
{ |
55
|
|
|
return (CollectionValidator::class === $validatorType) |
56
|
|
|
? parent::createValidator(MixedTypeCollectionValidator::class) |
57
|
|
|
: parent::createValidator($validatorType, $validatorOptions); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @inheritdoc |
62
|
|
|
*/ |
63
|
|
|
protected function buildBaseValidatorConjunction($indexKey, $targetClassName, array $validationGroups = []) |
64
|
|
|
{ |
65
|
|
|
parent::buildBaseValidatorConjunction($indexKey, $targetClassName, $validationGroups); |
66
|
|
|
|
67
|
|
|
/* |
68
|
|
|
* The code below is DIRTY: in order to use `SilentExceptionInterface` |
69
|
|
|
* feature we need an extended version of the `GenericObjectValidator`, |
70
|
|
|
* but this is hardcoded in: |
71
|
|
|
* \TYPO3\CMS\Extbase\Validation\ValidatorResolver::buildBaseValidatorConjunction() |
72
|
|
|
* |
73
|
|
|
* Here we replace every `GenericObjectValidator` by our own instance. |
74
|
|
|
* |
75
|
|
|
* Please, do not try this at home. |
76
|
|
|
*/ |
77
|
|
|
/** @var ConjunctionValidator $conjunctionValidator */ |
78
|
|
|
$conjunctionValidator = $this->baseValidatorConjunctions[$indexKey]; |
79
|
|
|
|
80
|
|
|
foreach ($conjunctionValidator->getValidators() as $validator) { |
81
|
|
|
if ($validator instanceof GenericObjectValidator) { |
82
|
|
|
/* |
83
|
|
|
* A full check is processed on the properties to check for |
84
|
|
|
* mixed types, in which case a validator is added to these |
85
|
|
|
* properties. |
86
|
|
|
*/ |
87
|
|
|
$this->addMixedTypeValidators($targetClassName, $validator); |
88
|
|
|
|
89
|
|
|
/** @var ConfigurationObjectValidator $newValidator */ |
90
|
|
|
$newValidator = $this->objectManager->get(ConfigurationObjectValidator::class, []); |
|
|
|
|
91
|
|
|
|
92
|
|
|
foreach ($validator->getPropertyValidators() as $propertyName => $propertyValidators) { |
93
|
|
|
foreach ($propertyValidators as $propertyValidator) { |
94
|
|
|
$newValidator->addPropertyValidator($propertyName, $propertyValidator); |
95
|
|
|
} |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
// Replacing the old validator with the new one... |
99
|
|
|
$conjunctionValidator->removeValidator($validator); |
100
|
|
|
unset($validator); |
101
|
|
|
$conjunctionValidator->addValidator($newValidator); |
102
|
|
|
} |
103
|
|
|
} |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* This function will list the properties of the given class, and filter on |
108
|
|
|
* the ones that do not have a validator assigned yet. |
109
|
|
|
* |
110
|
|
|
* @param string $targetClassName |
111
|
|
|
* @param GenericObjectValidator $validator |
112
|
|
|
*/ |
113
|
|
|
protected function addMixedTypeValidators($targetClassName, GenericObjectValidator $validator) |
114
|
|
|
{ |
115
|
|
|
foreach ($this->reflectionService->getClassPropertyNames($targetClassName) as $property) { |
116
|
|
|
/* |
117
|
|
|
* If the property already is already bound to an object validator, |
118
|
|
|
* there is no need to do further checks. |
119
|
|
|
*/ |
120
|
|
|
if ($this->propertyHasObjectValidator($validator, $property)) { |
121
|
|
|
continue; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
if ($this->propertyIsMixedType($targetClassName, $property)) { |
125
|
|
|
/* |
126
|
|
|
* The property is mixed, a validator with the `mixedTypes` |
127
|
|
|
* option is added, to delegate the validator resolving to |
128
|
|
|
* later (when the property is actually filled). |
129
|
|
|
*/ |
130
|
|
|
$objectValidator = $this->createValidator(MixedTypeValidator::class); |
131
|
|
|
|
132
|
|
|
$validator->addPropertyValidator($property, $objectValidator); |
|
|
|
|
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Checks among the existing validators of the given property if it does |
139
|
|
|
* already has an object validator (can be several types, like the classes |
140
|
|
|
* `ObjectValidator` or `ConjunctionValidator`, as long as they implement |
141
|
|
|
* the interface `ObjectValidatorInterface`). |
142
|
|
|
* |
143
|
|
|
* If one is found, `true` is returned. |
144
|
|
|
* |
145
|
|
|
* @param GenericObjectValidator $validator |
146
|
|
|
* @param string $property |
147
|
|
|
* @return bool |
148
|
|
|
*/ |
149
|
|
|
protected function propertyHasObjectValidator(GenericObjectValidator $validator, $property) |
150
|
|
|
{ |
151
|
|
|
$propertiesValidators = $validator->getPropertyValidators(); |
152
|
|
|
|
153
|
|
|
if (isset($propertiesValidators[$property])) { |
154
|
|
|
foreach ($propertiesValidators[$property] as $validator) { |
155
|
|
|
if ($validator instanceof ObjectValidatorInterface) { |
156
|
|
|
return true; |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
return false; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Checks if the given property is a mixed type. |
166
|
|
|
* |
167
|
|
|
* First, we check if the type of the property is a class that implements |
168
|
|
|
* the interface `MixedTypesInterface`. |
169
|
|
|
* |
170
|
|
|
* If not, we check if the property has the annotation `@mixedTypesResolver` |
171
|
|
|
* with a class name that implements the interface `MixedTypesInterface`. |
172
|
|
|
* |
173
|
|
|
* If one was found, `true` is returned. |
174
|
|
|
* |
175
|
|
|
* @param string $className |
176
|
|
|
* @param string $property |
177
|
|
|
* @return bool |
178
|
|
|
*/ |
179
|
|
|
protected function propertyIsMixedType($className, $property) |
180
|
|
|
{ |
181
|
|
|
$mixedType = false; |
182
|
|
|
|
183
|
|
|
$propertySchema = $this->reflectionService->getClassSchema($className)->getProperty($property); |
184
|
|
|
|
185
|
|
|
if ($this->classIsMixedType($propertySchema['type'])) { |
186
|
|
|
$mixedType = true; |
187
|
|
|
} else { |
188
|
|
|
if ($this->reflectionService->isPropertyTaggedWith($className, $property, MixedTypesService::PROPERTY_ANNOTATION_MIXED_TYPE)) { |
189
|
|
|
$tags = $this->reflectionService->getPropertyTagValues($className, $property, MixedTypesService::PROPERTY_ANNOTATION_MIXED_TYPE); |
190
|
|
|
$mixedTypeClassName = reset($tags); |
191
|
|
|
|
192
|
|
|
if ($this->classIsMixedType($mixedTypeClassName)) { |
193
|
|
|
$mixedType = true; |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
return $mixedType; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @param string $className |
203
|
|
|
* @return bool |
204
|
|
|
*/ |
205
|
|
|
protected function classIsMixedType($className) |
206
|
|
|
{ |
207
|
|
|
return Core::get()->classExists($className) |
208
|
|
|
&& array_key_exists(MixedTypesInterface::class, class_implements($className)); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @param ExtbaseReflectionService $reflectionService |
213
|
|
|
*/ |
214
|
|
|
public function injectReflectionService(ExtbaseReflectionService $reflectionService) |
215
|
|
|
{ |
216
|
|
|
$this->reflectionService = Core::get()->getReflectionService(); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* @param ObjectService $objectService |
221
|
|
|
*/ |
222
|
|
|
public function injectObjectService(ObjectService $objectService) |
223
|
|
|
{ |
224
|
|
|
$this->objectService = $objectService; |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.