Raised DBAL version from 1.1.5 to 1.1.6
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Validation / ValidatorResolver.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 team.
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * Validator resolver to automatically find a appropriate validator for a given subject
30 *
31 * @package Extbase
32 * @subpackage Validation
33 * @version $Id: ValidatorResolver.php 1790 2010-01-18 22:27:37Z jocrau $
34 */
35 class Tx_Extbase_Validation_ValidatorResolver {
36
37 /**
38 * Match validator names and options
39 * @var string
40 */
41 const PATTERN_MATCH_VALIDATORS = '/
42 (?:^|,\s*)
43 (?P<validatorName>[a-z0-9_]+)
44 \s*
45 (?:\(
46 (?P<validatorOptions>(?:\s*[a-z0-9]+\s*=\s*(?:
47 "(?:\\\\"|[^"])*"
48 |\'(?:\\\\\'|[^\'])*\'
49 |(?:\s|[^,"\']*)
50 )(?:\s|,)*)*)
51 \))?
52 /ixS';
53
54 /**
55 * Match validator options (to parse actual options)
56 * @var string
57 */
58 const PATTERN_MATCH_VALIDATOROPTIONS = '/
59 \s*
60 (?P<optionName>[a-z0-9]+)
61 \s*=\s*
62 (?P<optionValue>
63 "(?:\\\\"|[^"])*"
64 |\'(?:\\\\\'|[^\'])*\'
65 |(?:\s|[^,"\']*)
66 )
67 /ixS';
68
69 /**
70 * @var Tx_Extbase_Object_ManagerInterface
71 */
72 protected $objectManager;
73
74 /**
75 * @var Tx_Extbase_Reflection_Service
76 */
77 protected $reflectionService;
78
79 /**
80 * @var array
81 */
82 protected $baseValidatorConjunctions = array();
83
84 /**
85 * Injects the object manager
86 *
87 * @param Tx_Extbase_Object_ManagerInterface $objectManager A reference to the object manager
88 * @return void
89 */
90 public function injectObjectManager(Tx_Extbase_Object_ManagerInterface $objectManager) {
91 $this->objectManager = $objectManager;
92 }
93
94 /**
95 * Injects the reflection service
96 *
97 * @param Tx_Extbase_Reflection_Service $reflectionService
98 * @return void
99 */
100 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
101 $this->reflectionService = $reflectionService;
102 }
103
104 /**
105 * Get a validator for a given data type. Returns a validator implementing
106 * the Tx_Extbase_Validation_Validator_ValidatorInterface or NULL if no validator
107 * could be resolved.
108 *
109 * @param string $validatorName Either one of the built-in data types or fully qualified validator class name
110 * @param array $validatorOptions Options to be passed to the validator
111 * @return Tx_Extbase_Validation_Validator_ValidatorInterface Validator or NULL if none found.
112 */
113 public function createValidator($validatorName, array $validatorOptions = array()) {
114 $validatorClassName = $this->resolveValidatorObjectName($validatorName);
115 if ($validatorClassName === FALSE) return NULL;
116 $validator = $this->objectManager->getObject($validatorClassName);
117 if (!($validator instanceof Tx_Extbase_Validation_Validator_ValidatorInterface)) {
118 return NULL;
119 }
120
121 $validator->setOptions($validatorOptions);
122 return $validator;
123 }
124
125 /**
126 * Resolves and returns the base validator conjunction for the given data type.
127 *
128 * If no validator could be resolved (which usually means that no validation is necessary),
129 * NULL is returned.
130 *
131 * @param string $dataType The data type to search a validator for. Usually the fully qualified object name
132 * @return Tx_Extbase_Validation_Validator_ConjunctionValidator The validator conjunction or NULL
133 */
134 public function getBaseValidatorConjunction($dataType) {
135 if (!isset($this->baseValidatorConjunctions[$dataType])) {
136 $this->baseValidatorConjunctions[$dataType] = $this->buildBaseValidatorConjunction($dataType);
137 }
138 return $this->baseValidatorConjunctions[$dataType];
139 }
140
141 /**
142 * Detects and registers any validators for arguments:
143 * - by the data type specified in the @param annotations
144 * - additional validators specified in the @validate annotations of a method
145 *
146 * @return array An Array of ValidatorConjunctions for each method parameters.
147 */
148 public function buildMethodArgumentsValidatorConjunctions($className, $methodName) {
149 $validatorConjunctions = array();
150
151 $methodParameters = $this->reflectionService->getMethodParameters($className, $methodName);
152 $methodTagsValues = $this->reflectionService->getMethodTagsValues($className, $methodName);
153 if (!count($methodParameters)) {
154 // early return in case no parameters were found.
155 return $validatorConjunctions;
156 }
157 foreach ($methodParameters as $parameterName => $methodParameter) {
158 $validatorConjunction = $this->createValidator('Conjunction');
159 $typeValidator = $this->createValidator($methodParameter['type']);
160 if ($typeValidator !== NULL) $validatorConjunction->addValidator($typeValidator);
161 $validatorConjunctions[$parameterName] = $validatorConjunction;
162 }
163
164 if (isset($methodTagsValues['validate'])) {
165 foreach ($methodTagsValues['validate'] as $validateValue) {
166 $parsedAnnotation = $this->parseValidatorAnnotation($validateValue);
167 foreach ($parsedAnnotation['validators'] as $validatorConfiguration) {
168 $newValidator = $this->createValidator($validatorConfiguration['validatorName'], $validatorConfiguration['validatorOptions']);
169 if ($newValidator === NULL) throw new Tx_Extbase_Validation_Exception_NoSuchValidator('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Could not resolve class name for validator "' . $validatorConfiguration['validatorName'] . '".', 1239853109);
170
171 if (isset($validatorConjunctions[$parsedAnnotation['argumentName']])) {
172 $validatorConjunctions[$parsedAnnotation['argumentName']]->addValidator($newValidator);
173 } else {
174 throw new Tx_Extbase_Validation_Exception_InvalidValidationConfiguration('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Validator specified for argument name "' . $parsedAnnotation['argumentName'] . '", but this argument does not exist.', 1253172726);
175 }
176 }
177 }
178 }
179 return $validatorConjunctions;
180 }
181
182 /**
183 * Builds a base validator conjunction for the given data type.
184 *
185 * The base validation rules are those which were declared directly in a class (typically
186 * a model) through some @validate annotations on properties.
187 *
188 * Additionally, if a custom validator was defined for the class in question, it will be added
189 * to the end of the conjunction. A custom validator is found if it follows the naming convention
190 * "Replace '\Model\' by '\Validator\' and append "Validator".
191 *
192 * Example: $dataType is F3\Foo\Domain\Model\Quux, then the Validator will be found if it has the
193 * name F3\Foo\Domain\Validator\QuuxValidator
194 *
195 * @param string $dataType The data type to build the validation conjunction for. Needs to be the fully qualified object name.
196 * @return Tx_Extbase_Validation_Validator_ConjunctionValidator The validator conjunction or NULL
197 */
198 protected function buildBaseValidatorConjunction($dataType) {
199 $validatorConjunction = $this->objectManager->getObject('Tx_Extbase_Validation_Validator_ConjunctionValidator');
200
201 // Model based validator
202 if (strstr($dataType, '_') !== FALSE && class_exists($dataType)) {
203 $validatorCount = 0;
204 $objectValidator = $this->createValidator('GenericObject');
205
206 foreach ($this->reflectionService->getClassPropertyNames($dataType) as $classPropertyName) {
207 $classPropertyTagsValues = $this->reflectionService->getPropertyTagsValues($dataType, $classPropertyName);
208 if (!isset($classPropertyTagsValues['validate'])) continue;
209
210 foreach ($classPropertyTagsValues['validate'] as $validateValue) {
211 $parsedAnnotation = $this->parseValidatorAnnotation($validateValue);
212 foreach ($parsedAnnotation['validators'] as $validatorConfiguration) {
213 $newValidator = $this->createValidator($validatorConfiguration['validatorName'], $validatorConfiguration['validatorOptions']);
214 if ($newValidator === NULL) {
215 throw new Tx_Extbase_Validation_Exception_NoSuchValidator('Invalid validate annotation in ' . $dataType . '::' . $classPropertyName . ': Could not resolve class name for validator "' . $validatorConfiguration['validatorName'] . '".', 1241098027);
216 }
217 $objectValidator->addPropertyValidator($classPropertyName, $newValidator);
218 $validatorCount ++;
219 }
220 }
221 }
222 if ($validatorCount > 0) $validatorConjunction->addValidator($objectValidator);
223 }
224
225 // Custom validator for the class
226 $possibleValidatorClassName = str_replace('_Model_', '_Validator_', $dataType) . 'Validator';
227 $customValidator = $this->createValidator($possibleValidatorClassName);
228 if ($customValidator !== NULL) {
229 $validatorConjunction->addValidator($customValidator);
230 }
231
232 return $validatorConjunction;
233 }
234
235 /**
236 * Parses the validator options given in @validate annotations.
237 *
238 * @return array
239 */
240 protected function parseValidatorAnnotation($validateValue) {
241 $matches = array();
242 if ($validateValue[0] === '$') {
243 $parts = explode(' ', $validateValue, 2);
244 $validatorConfiguration = array('argumentName' => ltrim($parts[0], '$'), 'validators' => array());
245 preg_match_all(self::PATTERN_MATCH_VALIDATORS, $parts[1], $matches, PREG_SET_ORDER);
246 } else {
247 $validatorConfiguration = array('validators' => array());
248 preg_match_all(self::PATTERN_MATCH_VALIDATORS, $validateValue, $matches, PREG_SET_ORDER);
249 }
250
251 foreach ($matches as $match) {
252 $validatorOptions = array();
253 if (isset($match['validatorOptions'])) {
254 $validatorOptions = $this->parseValidatorOptions($match['validatorOptions']);
255 }
256 $validatorConfiguration['validators'][] = array('validatorName' => $match['validatorName'], 'validatorOptions' => $validatorOptions);
257 }
258
259 return $validatorConfiguration;
260 }
261
262 /**
263 * Parses $rawValidatorOptions not containing quoted option values.
264 * $rawValidatorOptions will be an empty string afterwards (pass by ref!).
265 *
266 * @param string &$rawValidatorOptions
267 * @return array An array of optionName/optionValue pairs
268 */
269 protected function parseValidatorOptions($rawValidatorOptions) {
270 $validatorOptions = array();
271 $parsedValidatorOptions = array();
272 preg_match_all(self::PATTERN_MATCH_VALIDATOROPTIONS, $rawValidatorOptions, $validatorOptions, PREG_SET_ORDER);
273 foreach ($validatorOptions as $validatorOption) {
274 $parsedValidatorOptions[trim($validatorOption['optionName'])] = trim($validatorOption['optionValue']);
275 }
276 array_walk($parsedValidatorOptions, array($this, 'unquoteString'));
277 return $parsedValidatorOptions;
278 }
279
280 /**
281 * Removes escapings from a given argument string and trims the outermost
282 * quotes.
283 *
284 * This method is meant as a helper for regular expression results.
285 *
286 * @param string &$quotedValue Value to unquote
287 */
288 protected function unquoteString(&$quotedValue) {
289 switch ($quotedValue[0]) {
290 case '"':
291 $quotedValue = str_replace('\"', '"', trim($quotedValue, '"'));
292 break;
293 case '\'':
294 $quotedValue = str_replace('\\\'', '\'', trim($quotedValue, '\''));
295 break;
296 }
297 $quotedValue = str_replace('\\\\', '\\', $quotedValue);
298 }
299
300 /**
301 *
302 *
303 * Returns an object of an appropriate validator for the given class. If no validator is available
304 * FALSE is returned
305 *
306 * @param string $validatorName Either the fully qualified class name of the validator or the short name of a built-in validator
307 * @return string Name of the validator object or FALSE
308 */
309 protected function resolveValidatorObjectName($validatorName) {
310 if (strstr($validatorName, '_') !== FALSE && class_exists($validatorName)) return $validatorName;
311
312 $possibleClassName = 'Tx_Extbase_Validation_Validator_' . $this->unifyDataType($validatorName) . 'Validator';
313 if (class_exists($possibleClassName)) return $possibleClassName;
314
315 return FALSE;
316 }
317
318 /**
319 * Preprocess data types. Used to map primitive PHP types to DataTypes used in Extbase.
320 *
321 * @param string $type Data type to unify
322 * @return string unified data type
323 */
324 protected function unifyDataType($type) {
325 switch ($type) {
326 case 'int' :
327 $type = 'Integer';
328 break;
329 case 'bool' :
330 $type = 'Boolean';
331 break;
332 case 'double' :
333 $type = 'Float';
334 break;
335 case 'numeric' :
336 $type = 'Number';
337 break;
338 case 'mixed' :
339 $type = 'Raw';
340 break;
341 }
342 return ucfirst($type);
343 }
344
345 }
346
347 ?>