‪TYPO3CMS  9.5
ValidatorResolver.php
Go to the documentation of this file.
1 <?php
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 
23 
29 {
37  (?:^|,\s*)
38  (?P<validatorName>[a-z0-9:.\\\\]+)
39  \s*
40  (?:\‍(
41  (?P<validatorOptions>(?:\s*[a-z0-9]+\s*=\s*(?:
42  "(?:\\\\"|[^"])*"
43  |\'(?:\\\\\'|[^\'])*\'
44  |(?:\s|[^,"\']*)
45  )(?:\s|,)*)*)
46  \‍))?
47  /ixS';
48 
55  \s*
56  (?P<optionName>[a-z0-9]+)
57  \s*=\s*
58  (?P<optionValue>
59  "(?:\\\\"|[^"])*"
60  |\'(?:\\\\\'|[^\'])*\'
61  |(?:\s|[^,"\']*)
62  )
63  /ixS';
64 
68  protected ‪$objectManager;
69 
73  protected ‪$reflectionService;
74 
78  protected ‪$baseValidatorConjunctions = [];
79 
83  public function ‪injectObjectManager(\‪TYPO3\CMS\‪Extbase\Object\ObjectManagerInterface ‪$objectManager)
84  {
85  $this->objectManager = ‪$objectManager;
86  }
87 
91  public function ‪injectReflectionService(\‪TYPO3\CMS\‪Extbase\Reflection\ReflectionService ‪$reflectionService)
92  {
93  $this->reflectionService = ‪$reflectionService;
94  }
95 
105  public function ‪createValidator($validatorType, array $validatorOptions = [])
106  {
107  try {
111  $validatorObjectName = $this->‪resolveValidatorObjectName($validatorType);
112 
113  ‪$validator = $this->objectManager->get($validatorObjectName, $validatorOptions);
114 
115  // Move this check into ClassSchema
116  if (!(‪$validator instanceof \‪TYPO3\CMS\‪Extbase\Validation\Validator\ValidatorInterface)) {
117  throw new Exception\NoSuchValidatorException('The validator "' . $validatorObjectName . '" does not implement TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface!', 1300694875);
118  }
119 
120  return ‪$validator;
121  } catch (NoSuchValidatorException $e) {
122  GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__)->debug($e->getMessage());
123  return null;
124  }
125  }
126 
136  public function ‪getBaseValidatorConjunction($targetClassName)
137  {
138  if (!array_key_exists($targetClassName, $this->baseValidatorConjunctions)) {
139  $this->‪buildBaseValidatorConjunction($targetClassName, $targetClassName);
140  }
141 
142  return $this->baseValidatorConjunctions[$targetClassName];
143  }
144 
160  public function ‪buildMethodArgumentsValidatorConjunctions($className, $methodName, array $methodParameters = null, array $methodValidateAnnotations = null)
161  {
162  trigger_error(
163  'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
164  E_USER_DEPRECATED
165  );
166 
168  $validatorConjunctions = [];
169 
170  if ($methodParameters === null) {
171  $methodParameters = $this->reflectionService
172  ->getClassSchema($className)
173  ->getMethod($methodName)['params'] ?? [];
174  }
175  if (empty($methodParameters)) {
176  return $validatorConjunctions;
177  }
178 
179  foreach ($methodParameters as $parameterName => $methodParameter) {
181  $validatorConjunction = $this->‪createValidator(ConjunctionValidator::class);
182 
183  if (!array_key_exists('type', $methodParameter)) {
184  throw new Exception\InvalidTypeHintException('Missing type information, probably no @param annotation for parameter "$' . $parameterName . '" in ' . $className . '->' . $methodName . '()', 1281962564);
185  }
186 
187  if (strpbrk($methodParameter['type'], '\\') === false) {
188  $typeValidator = $this->‪createValidator($methodParameter['type']);
189  } else {
190  $typeValidator = null;
191  }
192 
193  if ($typeValidator !== null) {
194  $validatorConjunction->addValidator($typeValidator);
195  }
196  $validatorConjunctions[$parameterName] = $validatorConjunction;
197  }
198 
199  if ($methodValidateAnnotations === null) {
200  $validateAnnotations = $this->‪getMethodValidateAnnotations($className, $methodName);
201  $methodValidateAnnotations = array_map(function ($validateAnnotation) {
202  return [
203  'type' => $validateAnnotation['validatorName'],
204  'options' => $validateAnnotation['validatorOptions'],
205  'argumentName' => $validateAnnotation['argumentName'],
206  ];
207  }, $validateAnnotations);
208  }
209 
210  foreach ($methodValidateAnnotations as $annotationParameters) {
211  $newValidator = $this->‪createValidator($annotationParameters['type'], $annotationParameters['options']);
212  if ($newValidator === null) {
213  throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Could not resolve class name for validator "' . $annotationParameters['type'] . '".', 1239853109);
214  }
215  if (isset($validatorConjunctions[$annotationParameters['argumentName']])) {
216  $validatorConjunctions[$annotationParameters['argumentName']]->addValidator($newValidator);
217  } elseif (strpos($annotationParameters['argumentName'], '.') !== false) {
218  $objectPath = explode('.', $annotationParameters['argumentName']);
219  $argumentName = array_shift($objectPath);
220  $validatorConjunctions[$argumentName]->addValidator($this->‪buildSubObjectValidator($objectPath, $newValidator));
221  } else {
222  throw new Exception\InvalidValidationConfigurationException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Validator specified for argument name "' . $annotationParameters['argumentName'] . '", but this argument does not exist.', 1253172726);
223  }
224  }
225 
226  return $validatorConjunctions;
227  }
228 
238  protected function ‪buildSubObjectValidator(array $objectPath, \‪TYPO3\CMS\‪Extbase\Validation\Validator\ValidatorInterface $propertyValidator)
239  {
240  trigger_error(
241  'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
242  E_USER_DEPRECATED
243  );
244 
245  $rootObjectValidator = $this->objectManager->get(\‪TYPO3\CMS\‪Extbase\Validation\Validator\GenericObjectValidator::class, []);
246  $parentObjectValidator = $rootObjectValidator;
247 
248  while (count($objectPath) > 1) {
249  $subObjectValidator = $this->objectManager->get(\‪TYPO3\CMS\‪Extbase\Validation\Validator\GenericObjectValidator::class, []);
250  $subPropertyName = array_shift($objectPath);
251  $parentObjectValidator->addPropertyValidator($subPropertyName, $subObjectValidator);
252  $parentObjectValidator = $subObjectValidator;
253  }
254 
255  $parentObjectValidator->addPropertyValidator(array_shift($objectPath), $propertyValidator);
256 
257  return $rootObjectValidator;
258  }
259 
282  protected function ‪buildBaseValidatorConjunction($indexKey, $targetClassName, array $validationGroups = [])
283  {
284  $conjunctionValidator = new ConjunctionValidator();
285  $this->baseValidatorConjunctions[$indexKey] = $conjunctionValidator;
286 
287  // note: the simpleType check reduces lookups to the class loader
288  if (!‪TypeHandlingUtility::isSimpleType($targetClassName) && class_exists($targetClassName)) {
289  $classSchema = $this->reflectionService->getClassSchema($targetClassName);
290 
291  // Model based validator
293  $objectValidator = $this->objectManager->get(\‪TYPO3\CMS\‪Extbase\Validation\Validator\GenericObjectValidator::class, []);
294  foreach ($classSchema->getProperties() as $classPropertyName => $classPropertyDefinition) {
296  $classPropertyTagsValues = $classPropertyDefinition['tags'];
297 
298  if (!isset($classPropertyTagsValues['var'])) {
299  throw new \InvalidArgumentException(sprintf('There is no @var annotation for property "%s" in class "%s".', $classPropertyName, $targetClassName), 1363778104);
300  }
301 
302  $propertyTargetClassName = $classPropertyDefinition['type'];
303  // note: the outer simpleType check reduces lookups to the class loader
304  if (!‪TypeHandlingUtility::isSimpleType($propertyTargetClassName)) {
305  if (‪TypeHandlingUtility::isCollectionType($propertyTargetClassName)) {
306  $collectionValidator = $this->‪createValidator(
307  \‪TYPO3\CMS\‪Extbase\Validation\Validator\CollectionValidator::class,
308  [
309  'elementType' => $classPropertyDefinition['elementType'],
310  'validationGroups' => $validationGroups
311  ]
312  );
313  $objectValidator->addPropertyValidator($classPropertyName, $collectionValidator);
314  } elseif (class_exists($propertyTargetClassName) && !‪TypeHandlingUtility::isCoreType($propertyTargetClassName) && $this->objectManager->isRegistered($propertyTargetClassName) && $this->objectManager->getScope($propertyTargetClassName) === \‪TYPO3\CMS\‪Extbase\Object\Container\Container::SCOPE_PROTOTYPE) {
315  $validatorForProperty = $this->‪getBaseValidatorConjunction($propertyTargetClassName);
316  if ($validatorForProperty !== null && $validatorForProperty->count() > 0) {
317  $objectValidator->addPropertyValidator($classPropertyName, $validatorForProperty);
318  }
319  }
320  }
321 
322  foreach ($classPropertyDefinition['validators'] as $validatorDefinition) {
323  // @todo: Respect validationGroups
324 
325  // @todo: At this point we already have the class name of the validator, thus there is not need
326  // @todo: calling \TYPO3\CMS\Extbase\Validation\ValidatorResolver::resolveValidatorObjectName inside
327  // @todo: \TYPO3\CMS\Extbase\Validation\ValidatorResolver::createValidator once again. However, to
328  // @todo: keep things simple for now, we still use the method createValidator here. In the future,
329  // @todo: createValidator must only accept FQCN's.
330  $newValidator = $this->‪createValidator($validatorDefinition['className'], $validatorDefinition['options']);
331  if ($newValidator === null) {
332  throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $targetClassName . '::' . $classPropertyName . ': Could not resolve class name for validator "' . $validatorDefinition['className'] . '".', 1241098027);
333  }
334  $objectValidator->addPropertyValidator($classPropertyName, $newValidator);
335  }
336  }
337 
338  if (!empty($objectValidator->getPropertyValidators())) {
339  $conjunctionValidator->addValidator($objectValidator);
340  }
341  }
342 
343  $this->‪addCustomValidators($targetClassName, $conjunctionValidator);
344  }
345 
359  protected function ‪addCustomValidators($targetClassName, ConjunctionValidator &$conjunctionValidator)
360  {
361  // @todo: get rid of ClassNamingUtility usage once we dropped underscored class name support
362  $possibleValidatorClassName = ‪ClassNamingUtility::translateModelNameToValidatorName($targetClassName);
363 
364  $customValidator = $this->‪createValidator($possibleValidatorClassName);
365  if ($customValidator !== null) {
366  $conjunctionValidator->addValidator($customValidator);
367  }
368 
369  // @todo: find polytype validator for class
370  }
371 
380  public function ‪parseValidatorAnnotation($validateValue)
381  {
382  trigger_error(
383  'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
384  E_USER_DEPRECATED
385  );
386 
387  $matches = [];
388  if ($validateValue[0] === '$') {
389  $parts = explode(' ', $validateValue, 2);
390  $validatorConfiguration = ['argumentName' => ltrim($parts[0], '$'), 'validators' => []];
391  preg_match_all(self::PATTERN_MATCH_VALIDATORS, $parts[1], $matches, PREG_SET_ORDER);
392  } else {
393  $validatorConfiguration = ['validators' => []];
394  preg_match_all(self::PATTERN_MATCH_VALIDATORS, $validateValue, $matches, PREG_SET_ORDER);
395  }
396  foreach ($matches as $match) {
397  $validatorOptions = [];
398  if (isset($match['validatorOptions'])) {
399  $validatorOptions = $this->‪parseValidatorOptions($match['validatorOptions']);
400  }
401  $validatorConfiguration['validators'][] = ['validatorName' => $match['validatorName'], 'validatorOptions' => $validatorOptions];
402  }
403  return $validatorConfiguration;
404  }
405 
414  protected function ‪parseValidatorOptions($rawValidatorOptions)
415  {
416  trigger_error(
417  'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
418  E_USER_DEPRECATED
419  );
420 
421  $validatorOptions = [];
422  $parsedValidatorOptions = [];
423  preg_match_all(self::PATTERN_MATCH_VALIDATOROPTIONS, $rawValidatorOptions, $validatorOptions, PREG_SET_ORDER);
424  foreach ($validatorOptions as $validatorOption) {
425  $parsedValidatorOptions[trim($validatorOption['optionName'])] = trim($validatorOption['optionValue']);
426  }
427  array_walk($parsedValidatorOptions, [$this, 'unquoteString']);
428  return $parsedValidatorOptions;
429  }
430 
440  protected function ‪unquoteString(&$quotedValue)
441  {
442  trigger_error(
443  'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
444  E_USER_DEPRECATED
445  );
446 
447  switch ($quotedValue[0]) {
448  case '"':
449  $quotedValue = str_replace('\\"', '"', trim($quotedValue, '"'));
450  break;
451  case '\'':
452  $quotedValue = str_replace('\\\'', '\'', trim($quotedValue, '\''));
453  break;
454  }
455  $quotedValue = str_replace('\\\\', '\\', $quotedValue);
456  }
457 
468  public function ‪resolveValidatorObjectName($validatorName)
469  {
470  if (strpos($validatorName, ':') !== false) {
471  // Found shorthand validator, either extbase or foreign extension
472  // NotEmpty or Acme.MyPck.Ext:MyValidator
473  list($extensionName, $extensionValidatorName) = explode(':', $validatorName);
474 
475  if ($validatorName !== $extensionName && $extensionValidatorName !== '') {
476  // Shorthand custom
477  if (strpos($extensionName, '.') !== false) {
478  $extensionNameParts = explode('.', $extensionName);
479  $extensionName = array_pop($extensionNameParts);
480  $vendorName = implode('\\', $extensionNameParts);
481  $possibleClassName = $vendorName . '\\' . $extensionName . '\\Validation\\Validator\\' . $extensionValidatorName;
482  }
483  } else {
484  // Shorthand built in
485  $possibleClassName = 'TYPO3\\CMS\\Extbase\\Validation\\Validator\\' . $this->‪getValidatorType($validatorName);
486  }
487  } elseif (strpbrk($validatorName, '\\') === false) {
488  // Shorthand built in
489  $possibleClassName = 'TYPO3\\CMS\\Extbase\\Validation\\Validator\\' . $this->‪getValidatorType($validatorName);
490  } else {
491  // Full qualified
492  // Example: \Acme\Ext\Validation\Validator\FooValidator
493  $possibleClassName = $validatorName;
494  if (!empty($possibleClassName) && $possibleClassName[0] === '\\') {
495  $possibleClassName = substr($possibleClassName, 1);
496  }
497  }
498 
499  if (substr($possibleClassName, - strlen('Validator')) !== 'Validator') {
500  $possibleClassName .= 'Validator';
501  }
502 
503  if (class_exists($possibleClassName)) {
504  $possibleClassNameInterfaces = class_implements($possibleClassName);
505  if (!in_array(\‪TYPO3\CMS\‪Extbase\Validation\Validator\ValidatorInterface::class, $possibleClassNameInterfaces)) {
506  // The guessed validatorname is a valid class name, but does not implement the ValidatorInterface
507  throw new NoSuchValidatorException('Validator class ' . $validatorName . ' must implement \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface', 1365776838);
508  }
509  $resolvedValidatorName = $possibleClassName;
510  } else {
511  throw new NoSuchValidatorException('Validator class ' . $validatorName . ' does not exist', 1365799920);
512  }
513 
514  return $resolvedValidatorName;
515  }
516 
523  protected function ‪getValidatorType($type)
524  {
525  switch ($type) {
526  case 'int':
527  $type = 'Integer';
528  break;
529  case 'bool':
530  $type = 'Boolean';
531  break;
532  case 'double':
533  $type = 'Float';
534  break;
535  case 'numeric':
536  $type = 'Number';
537  break;
538  case 'mixed':
539  $type = 'Raw';
540  break;
541  default:
542  $type = ucfirst($type);
543  }
544  return $type;
545  }
546 
556  public function ‪getMethodValidateAnnotations($className, $methodName)
557  {
558  $validateAnnotations = [];
559  $methodTagsValues = $this->reflectionService->getClassSchema($className)->getMethod($methodName)['tags'] ?? [];
560  if (isset($methodTagsValues['validate']) && is_array($methodTagsValues['validate'])) {
561  foreach ($methodTagsValues['validate'] as $validateValue) {
562  $parsedAnnotations = $this->‪parseValidatorAnnotation($validateValue);
563 
564  foreach ($parsedAnnotations['validators'] as ‪$validator) {
565  $validateAnnotations[] = [
566  'argumentName' => $parsedAnnotations['argumentName'],
567  'validatorName' => ‪$validator['validatorName'],
568  'validatorOptions' => ‪$validator['validatorOptions']
569  ];
570  }
571  }
572  }
573 
574  return $validateAnnotations;
575  }
576 }
‪TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator
Definition: ConjunctionValidator.php:23
‪TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException
Definition: NoSuchValidatorException.php:21
‪TYPO3\CMS\Extbase\Annotation
Definition: IgnoreValidation.php:4
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\$baseValidatorConjunctions
‪array $baseValidatorConjunctions
Definition: ValidatorResolver.php:75
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver
Definition: ValidatorResolver.php:29
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\unquoteString
‪unquoteString(&$quotedValue)
Definition: ValidatorResolver.php:437
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\createValidator
‪TYPO3 CMS Extbase Validation Validator ValidatorInterface createValidator($validatorType, array $validatorOptions=[])
Definition: ValidatorResolver.php:102
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\buildMethodArgumentsValidatorConjunctions
‪ConjunctionValidator[] buildMethodArgumentsValidatorConjunctions($className, $methodName, array $methodParameters=null, array $methodValidateAnnotations=null)
Definition: ValidatorResolver.php:157
‪TYPO3
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\PATTERN_MATCH_VALIDATOROPTIONS
‪const PATTERN_MATCH_VALIDATOROPTIONS
Definition: ValidatorResolver.php:54
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\resolveValidatorObjectName
‪string resolveValidatorObjectName($validatorName)
Definition: ValidatorResolver.php:465
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\injectReflectionService
‪injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
Definition: ValidatorResolver.php:88
‪TYPO3\CMS\Extbase\Utility\TypeHandlingUtility\isSimpleType
‪static bool isSimpleType($type)
Definition: TypeHandlingUtility.php:103
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\buildBaseValidatorConjunction
‪buildBaseValidatorConjunction($indexKey, $targetClassName, array $validationGroups=[])
Definition: ValidatorResolver.php:279
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\parseValidatorOptions
‪array parseValidatorOptions($rawValidatorOptions)
Definition: ValidatorResolver.php:411
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\addCustomValidators
‪addCustomValidators($targetClassName, ConjunctionValidator &$conjunctionValidator)
Definition: ValidatorResolver.php:356
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\getMethodValidateAnnotations
‪array getMethodValidateAnnotations($className, $methodName)
Definition: ValidatorResolver.php:553
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\getValidatorType
‪string getValidatorType($type)
Definition: ValidatorResolver.php:520
‪TYPO3\CMS\Extbase\Utility\TypeHandlingUtility
Definition: TypeHandlingUtility.php:19
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\parseValidatorAnnotation
‪array parseValidatorAnnotation($validateValue)
Definition: ValidatorResolver.php:377
‪$validator
‪if(isset($args['d'])) $validator
Definition: validateRstFiles.php:218
‪TYPO3\CMS\Core\Utility\ClassNamingUtility
Definition: ClassNamingUtility.php:23
‪TYPO3\CMS\Extbase\Utility\TypeHandlingUtility\isCoreType
‪static bool isCoreType($type)
Definition: TypeHandlingUtility.php:114
‪TYPO3\CMS\Extbase\Utility\TypeHandlingUtility\isCollectionType
‪static bool isCollectionType($type)
Definition: TypeHandlingUtility.php:125
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\injectObjectManager
‪injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
Definition: ValidatorResolver.php:80
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\$objectManager
‪TYPO3 CMS Extbase Object ObjectManagerInterface $objectManager
Definition: ValidatorResolver.php:67
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\PATTERN_MATCH_VALIDATORS
‪const PATTERN_MATCH_VALIDATORS
Definition: ValidatorResolver.php:36
‪TYPO3\CMS\Extbase\Validation
Definition: Error.php:2
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\getBaseValidatorConjunction
‪ConjunctionValidator getBaseValidatorConjunction($targetClassName)
Definition: ValidatorResolver.php:133
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪TYPO3\CMS\Core\Log\LogManager
Definition: LogManager.php:25
‪TYPO3\CMS\Extbase\Validation\Validator\AbstractCompositeValidator\addValidator
‪addValidator(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface $validator)
Definition: AbstractCompositeValidator.php:82
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\$reflectionService
‪TYPO3 CMS Extbase Reflection ReflectionService $reflectionService
Definition: ValidatorResolver.php:71
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Extbase\Validation\ValidatorResolver\buildSubObjectValidator
‪TYPO3 CMS Extbase Validation Validator GenericObjectValidator buildSubObjectValidator(array $objectPath, \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface $propertyValidator)
Definition: ValidatorResolver.php:235
‪TYPO3\CMS\Core\Utility\ClassNamingUtility\translateModelNameToValidatorName
‪static string translateModelNameToValidatorName($modelName)
Definition: ClassNamingUtility.php:49