TYPO3 CMS  TYPO3_6-2
ValidatorResolver.php
Go to the documentation of this file.
1 <?php
3 
22 
27 
35  (?:^|,\s*)
36  (?P<validatorName>[a-z0-9_:.\\\\]+)
37  \s*
38  (?:\(
39  (?P<validatorOptions>(?:\s*[a-z0-9]+\s*=\s*(?:
40  "(?:\\\\"|[^"])*"
41  |\'(?:\\\\\'|[^\'])*\'
42  |(?:\s|[^,"\']*)
43  )(?:\s|,)*)*)
44  \))?
45  /ixS';
46 
52  \s*
53  (?P<optionName>[a-z0-9]+)
54  \s*=\s*
55  (?P<optionValue>
56  "(?:\\\\"|[^"])*"
57  |\'(?:\\\\\'|[^\'])*\'
58  |(?:\s|[^,"\']*)
59  )
60  /ixS';
61 
66  protected $objectManager;
67 
72  protected $reflectionService;
73 
77  protected $baseValidatorConjunctions = array();
78 
88  public function createValidator($validatorType, array $validatorOptions = array()) {
89  try {
93  $validatorObjectName = $this->resolveValidatorObjectName($validatorType);
94 
95  $validator = $this->objectManager->get($validatorObjectName, $validatorOptions);
96 
97  if (!($validator instanceof \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface)) {
98  throw new Exception\NoSuchValidatorException('The validator "' . $validatorObjectName . '" does not implement TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface!', 1300694875);
99  }
100 
101  return $validator;
102  } catch (NoSuchValidatorException $e) {
103  GeneralUtility::devLog($e->getMessage(), 'extbase', GeneralUtility::SYSLOG_SEVERITY_INFO);
104  return NULL;
105  }
106  }
107 
117  public function getBaseValidatorConjunction($targetClassName) {
118  if (!array_key_exists($targetClassName, $this->baseValidatorConjunctions)) {
119  $this->buildBaseValidatorConjunction($targetClassName, $targetClassName);
120  }
121 
122  return $this->baseValidatorConjunctions[$targetClassName];
123  }
124 
139  public function buildMethodArgumentsValidatorConjunctions($className, $methodName, array $methodParameters = NULL, array $methodValidateAnnotations = NULL) {
140  $validatorConjunctions = array();
141 
142  if ($methodParameters === NULL) {
143  $methodParameters = $this->reflectionService->getMethodParameters($className, $methodName);
144  }
145  if (count($methodParameters) === 0) {
146  return $validatorConjunctions;
147  }
148 
149  foreach ($methodParameters as $parameterName => $methodParameter) {
150  $validatorConjunction = $this->createValidator('TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator');
151 
152  if (!array_key_exists('type', $methodParameter)) {
153  throw new Exception\InvalidTypeHintException('Missing type information, probably no @param annotation for parameter "$' . $parameterName . '" in ' . $className . '->' . $methodName . '()', 1281962564);
154  }
155 
156  // @todo: remove check for old underscore model name syntax once it's possible
157  if (strpbrk($methodParameter['type'], '_\\') === FALSE) {
158  $typeValidator = $this->createValidator($methodParameter['type']);
159  } else {
160  $typeValidator = NULL;
161  }
162 
163  if ($typeValidator !== NULL) {
164  $validatorConjunction->addValidator($typeValidator);
165  }
166  $validatorConjunctions[$parameterName] = $validatorConjunction;
167  }
168 
169  if ($methodValidateAnnotations === NULL) {
170  $validateAnnotations = $this->getMethodValidateAnnotations($className, $methodName);
171  $methodValidateAnnotations = array_map(function($validateAnnotation) {
172  return array(
173  'type' => $validateAnnotation['validatorName'],
174  'options' => $validateAnnotation['validatorOptions'],
175  'argumentName' => $validateAnnotation['argumentName'],
176  );
177  }, $validateAnnotations);
178  }
179 
180  foreach ($methodValidateAnnotations as $annotationParameters) {
181  $newValidator = $this->createValidator($annotationParameters['type'], $annotationParameters['options']);
182  if ($newValidator === NULL) {
183  throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Could not resolve class name for validator "' . $annotationParameters['type'] . '".', 1239853109);
184  }
185  if (isset($validatorConjunctions[$annotationParameters['argumentName']])) {
186  $validatorConjunctions[$annotationParameters['argumentName']]->addValidator($newValidator);
187  } elseif (strpos($annotationParameters['argumentName'], '.') !== FALSE) {
188  $objectPath = explode('.', $annotationParameters['argumentName']);
189  $argumentName = array_shift($objectPath);
190  $validatorConjunctions[$argumentName]->addValidator($this->buildSubObjectValidator($objectPath, $newValidator));
191  } else {
192  throw new Exception\InvalidValidationConfigurationException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Validator specified for argument name "' . $annotationParameters['argumentName'] . '", but this argument does not exist.', 1253172726);
193  }
194  }
195 
196  return $validatorConjunctions;
197  }
198 
207  protected function buildSubObjectValidator(array $objectPath, \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface $propertyValidator) {
208  $rootObjectValidator = $this->objectManager->get('TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator', array());
209  $parentObjectValidator = $rootObjectValidator;
210 
211  while (count($objectPath) > 1) {
212  $subObjectValidator = $this->objectManager->get('TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator', array());
213  $subPropertyName = array_shift($objectPath);
214  $parentObjectValidator->addPropertyValidator($subPropertyName, $subObjectValidator);
215  $parentObjectValidator = $subObjectValidator;
216  }
217 
218  $parentObjectValidator->addPropertyValidator(array_shift($objectPath), $propertyValidator);
219 
220  return $rootObjectValidator;
221  }
222 
246  protected function buildBaseValidatorConjunction($indexKey, $targetClassName, array $validationGroups = array()) {
247  $conjunctionValidator = new \TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator();
248  $this->baseValidatorConjunctions[$indexKey] = $conjunctionValidator;
249 
250  // note: the simpleType check reduces lookups to the class loader
251  if (!TypeHandlingUtility::isSimpleType($targetClassName) && class_exists($targetClassName)) {
252  // Model based validator
254  $objectValidator = $this->objectManager->get('TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator', array());
255  foreach ($this->reflectionService->getClassPropertyNames($targetClassName) as $classPropertyName) {
256  $classPropertyTagsValues = $this->reflectionService->getPropertyTagsValues($targetClassName, $classPropertyName);
257 
258  if (!isset($classPropertyTagsValues['var'])) {
259  throw new \InvalidArgumentException(sprintf('There is no @var annotation for property "%s" in class "%s".', $classPropertyName, $targetClassName), 1363778104);
260  }
261  try {
262  $parsedType = TypeHandlingUtility::parseType(trim(implode('', $classPropertyTagsValues['var']), ' \\'));
263  } catch (\TYPO3\CMS\Extbase\Utility\Exception\InvalidTypeException $exception) {
264  throw new \InvalidArgumentException(sprintf(' @var annotation of ' . $exception->getMessage(), 'class "' . $targetClassName . '", property "' . $classPropertyName . '"'), 1315564744, $exception);
265  }
266  $propertyTargetClassName = $parsedType['type'];
267  // note: the outer simpleType check reduces lookups to the class loader
268  if (!TypeHandlingUtility::isSimpleType($propertyTargetClassName)) {
269  if (TypeHandlingUtility::isCollectionType($propertyTargetClassName)) {
270  $collectionValidator = $this->createValidator('TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator', array('elementType' => $parsedType['elementType'], 'validationGroups' => $validationGroups));
271  $objectValidator->addPropertyValidator($classPropertyName, $collectionValidator);
272  } elseif (class_exists($propertyTargetClassName) && !TypeHandlingUtility::isCoreType($propertyTargetClassName) && $this->objectManager->isRegistered($propertyTargetClassName) && $this->objectManager->getScope($propertyTargetClassName) === \TYPO3\CMS\Extbase\Object\Container\Container::SCOPE_PROTOTYPE) {
273  $validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName, $validationGroups);
274  if (count($validatorForProperty) > 0) {
275  $objectValidator->addPropertyValidator($classPropertyName, $validatorForProperty);
276  }
277  }
278  }
279 
280  $validateAnnotations = array();
281  // @todo: Resolve annotations via reflectionService once its available
282  if (isset($classPropertyTagsValues['validate']) && is_array($classPropertyTagsValues['validate'])) {
283  foreach ($classPropertyTagsValues['validate'] as $validateValue) {
284  $parsedAnnotations = $this->parseValidatorAnnotation($validateValue);
285 
286  foreach ($parsedAnnotations['validators'] as $validator) {
287  array_push($validateAnnotations, array(
288  'argumentName' => $parsedAnnotations['argumentName'],
289  'validatorName' => $validator['validatorName'],
290  'validatorOptions' => $validator['validatorOptions']
291  ));
292  }
293  }
294  }
295 
296  foreach ($validateAnnotations as $validateAnnotation) {
297  // @todo: Respect validationGroups
298  $newValidator = $this->createValidator($validateAnnotation['validatorName'], $validateAnnotation['validatorOptions']);
299  if ($newValidator === NULL) {
300  throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $targetClassName . '::' . $classPropertyName . ': Could not resolve class name for validator "' . $validateAnnotation->type . '".', 1241098027);
301  }
302  $objectValidator->addPropertyValidator($classPropertyName, $newValidator);
303  }
304  }
305 
306  if (count($objectValidator->getPropertyValidators()) > 0) {
307  $conjunctionValidator->addValidator($objectValidator);
308  }
309  }
310 
311  $this->addCustomValidators($targetClassName, $conjunctionValidator);
312  }
313 
328  protected function addCustomValidators($targetClassName, ConjunctionValidator &$conjunctionValidator) {
329 
330  $addedValidatorClassName = NULL;
331  // @todo: get rid of ClassNamingUtility usage once we dropped underscored class name support
332  $possibleValidatorClassName = ClassNamingUtility::translateModelNameToValidatorName($targetClassName);
333 
334  $customValidator = $this->createValidator($possibleValidatorClassName);
335  if ($customValidator !== NULL) {
336  $conjunctionValidator->addValidator($customValidator);
337  $addedValidatorClassName = get_class($customValidator);
338  }
339 
340  // @todo: find polytype validator for class
341  }
342 
349  protected function parseValidatorAnnotation($validateValue) {
350  $matches = array();
351  if ($validateValue[0] === '$') {
352  $parts = explode(' ', $validateValue, 2);
353  $validatorConfiguration = array('argumentName' => ltrim($parts[0], '$'), 'validators' => array());
354  preg_match_all(self::PATTERN_MATCH_VALIDATORS, $parts[1], $matches, PREG_SET_ORDER);
355  } else {
356  $validatorConfiguration = array('validators' => array());
357  preg_match_all(self::PATTERN_MATCH_VALIDATORS, $validateValue, $matches, PREG_SET_ORDER);
358  }
359  foreach ($matches as $match) {
360  $validatorOptions = array();
361  if (isset($match['validatorOptions'])) {
362  $validatorOptions = $this->parseValidatorOptions($match['validatorOptions']);
363  }
364  $validatorConfiguration['validators'][] = array('validatorName' => $match['validatorName'], 'validatorOptions' => $validatorOptions);
365  }
366  return $validatorConfiguration;
367  }
368 
376  protected function parseValidatorOptions($rawValidatorOptions) {
377  $validatorOptions = array();
378  $parsedValidatorOptions = array();
379  preg_match_all(self::PATTERN_MATCH_VALIDATOROPTIONS, $rawValidatorOptions, $validatorOptions, PREG_SET_ORDER);
380  foreach ($validatorOptions as $validatorOption) {
381  $parsedValidatorOptions[trim($validatorOption['optionName'])] = trim($validatorOption['optionValue']);
382  }
383  array_walk($parsedValidatorOptions, array($this, 'unquoteString'));
384  return $parsedValidatorOptions;
385  }
386 
396  protected function unquoteString(&$quotedValue) {
397  switch ($quotedValue[0]) {
398  case '"':
399  $quotedValue = str_replace('\\"', '"', trim($quotedValue, '"'));
400  break;
401  case '\'':
402  $quotedValue = str_replace('\\\'', '\'', trim($quotedValue, '\''));
403  break;
404  }
405  $quotedValue = str_replace('\\\\', '\\', $quotedValue);
406  }
407 
417  protected function resolveValidatorObjectName($validatorName) {
418  if (strpos($validatorName, ':') !== FALSE || strpbrk($validatorName, '_\\') === FALSE) {
423  list($extensionName, $extensionValidatorName) = explode(':', $validatorName);
424 
425  if ($validatorName !== $extensionName && strlen($extensionValidatorName) > 0) {
429  if (strpos($extensionName, '.') !== FALSE) {
430  $extensionNameParts = explode('.', $extensionName);
431  $extensionName = array_pop($extensionNameParts);
432  $vendorName = implode('\\', $extensionNameParts);
433  $possibleClassName = $vendorName . '\\' . $extensionName . '\\Validation\\Validator\\' . $extensionValidatorName;
434  } else {
435  $possibleClassName = 'Tx_' . $extensionName . '_Validation_Validator_' . $extensionValidatorName;
436  }
437  } else {
441  $possibleClassName = 'TYPO3\\CMS\\Extbase\\Validation\\Validator\\' . $this->getValidatorType($validatorName);
442  }
443  } else {
448  $possibleClassName = $validatorName;
449  }
450 
451  if (substr($possibleClassName, - strlen('Validator')) !== 'Validator') {
452  $possibleClassName .= 'Validator';
453  }
454 
455  if (class_exists($possibleClassName)) {
456  $possibleClassNameInterfaces = class_implements($possibleClassName);
457  if (!in_array('TYPO3\\CMS\\Extbase\\Validation\\Validator\\ValidatorInterface', $possibleClassNameInterfaces)) {
458  // The guessed validatorname is a valid class name, but does not implement the ValidatorInterface
459  throw new NoSuchValidatorException('Validator class ' . $validatorName . ' must implement \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface', 1365776838);
460  }
461  $resolvedValidatorName = $possibleClassName;
462  } else {
463  throw new NoSuchValidatorException('Validator class ' . $validatorName . ' does not exist', 1365799920);
464  }
465 
466  return $resolvedValidatorName;
467  }
468 
475  protected function getValidatorType($type) {
476  switch ($type) {
477  case 'int':
478  $type = 'Integer';
479  break;
480  case 'bool':
481  $type = 'Boolean';
482  break;
483  case 'double':
484  $type = 'Float';
485  break;
486  case 'numeric':
487  $type = 'Number';
488  break;
489  case 'mixed':
490  $type = 'Raw';
491  break;
492  default:
493  $type = ucfirst($type);
494  }
495  return $type;
496  }
497 
506  public function getMethodValidateAnnotations($className, $methodName) {
507  $validateAnnotations = array();
508  $methodTagsValues = $this->reflectionService->getMethodTagsValues($className, $methodName);
509  if (isset($methodTagsValues['validate']) && is_array($methodTagsValues['validate'])) {
510  foreach ($methodTagsValues['validate'] as $validateValue) {
511  $parsedAnnotations = $this->parseValidatorAnnotation($validateValue);
512 
513  foreach ($parsedAnnotations['validators'] as $validator) {
514  array_push($validateAnnotations, array(
515  'argumentName' => $parsedAnnotations['argumentName'],
516  'validatorName' => $validator['validatorName'],
517  'validatorOptions' => $validator['validatorOptions']
518  ));
519  }
520  }
521  }
522 
523  return $validateAnnotations;
524  }
525 }
buildMethodArgumentsValidatorConjunctions($className, $methodName, array $methodParameters=NULL, array $methodValidateAnnotations=NULL)
static devLog($msg, $extKey, $severity=0, $dataVar=FALSE)
addCustomValidators($targetClassName, ConjunctionValidator &$conjunctionValidator)
buildSubObjectValidator(array $objectPath, \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface $propertyValidator)
createValidator($validatorType, array $validatorOptions=array())
addValidator(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface $validator)