setOptions($validatorOptions); $validator->setRequest($request); return $validator; } catch (NoSuchValidatorException $e) { GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__)->debug($e->getMessage()); return null; } } /** * Resolves and returns the base validator conjunction for the given data type. * If no validator could be resolved (which usually means that no validation is necessary), NULL is returned. * * @param string $targetClassName The data type to search a validator for. Usually the fully qualified object name */ public function getBaseValidatorConjunction( string $targetClassName, ?ServerRequestInterface $request = null ): ConjunctionValidator { if (!isset($this->baseValidatorConjunctions[$targetClassName])) { $conjunctionValidator = GeneralUtility::makeInstance(ConjunctionValidator::class); $this->baseValidatorConjunctions[$targetClassName] = $conjunctionValidator; // The simpleType check reduces lookups to the class loader if (!TypeHandlingUtility::isSimpleType($targetClassName) && class_exists($targetClassName)) { $this->buildBaseValidatorConjunction($conjunctionValidator, $targetClassName, $request); } } return $this->baseValidatorConjunctions[$targetClassName]; } /** * Builds a base validator conjunction for the given data type. * * The base validation rules are those which were declared directly in a class (typically * a model) through some #[Validate] attributes on properties. * * If a property holds a class for which a base validator exists, that property will be * checked as well, regardless of a validation attribute. * * Additionally, if a custom validator was defined for the class in question, it will be added * to the end of the conjunction. A custom validator is found if it follows the naming convention * "Replace '\Model\' by '\Validator\' and append 'Validator'". * * Example: $targetClassName is TYPO3\Foo\Domain\Model\Quux, then the validator will be found if it has the * name TYPO3\Foo\Domain\Validator\QuuxValidator * * @param class-string $targetClassName The data type to build the validation conjunction for. Needs to be the fully qualified class name. * @throws NoSuchValidatorException * @throws \InvalidArgumentException */ protected function buildBaseValidatorConjunction( ConjunctionValidator $conjunctionValidator, string $targetClassName, ?ServerRequestInterface $request ): void { $classSchema = $this->reflectionService->getClassSchema($targetClassName); // Model based validator /** @var GenericObjectValidator $objectValidator */ $objectValidator = $this->createValidator(GenericObjectValidator::class); foreach ($classSchema->getProperties() as $property) { $primaryType = $property->getPrimaryType(); if (!$primaryType instanceof TypeAdapter) { // @todo: The type is only necessary here for further analyzing whether it's a simple type or // a collection. If this is evaluated in the ClassSchema, this whole code part is not needed // any longer and can be removed. throw new \InvalidArgumentException( sprintf('There is no @var annotation or type declaration for property "%s" in class "%s".', $property->getName(), $targetClassName), 1363778104 ); } $propertyTargetClassName = $primaryType->getClassName() ?? $primaryType->getBuiltinType(); // Skip transient properties for auto-generated validators (model-typed properties). // Transient properties are not persisted and may not have public accessors. if (!TypeHandlingUtility::isSimpleType($propertyTargetClassName) && !$property->isTransient()) { // The outer simpleType check reduces lookups to the class loader // @todo: Whether the property holds a simple type or not and whether it holds a collection is known in // in the ClassSchema. The information could be made available and not evaluated here again. $primaryCollectionValueType = $property->getPrimaryCollectionValueType(); if ($primaryType->isCollection() && $primaryCollectionValueType instanceof TypeAdapter) { /** @var CollectionValidator $collectionValidator */ $collectionValidator = $this->createValidator( CollectionValidator::class, [ 'elementType' => $primaryCollectionValueType->getClassName() ?? $primaryCollectionValueType->getBuiltinType(), ], $request ); $objectValidator->addPropertyValidator($property->getName(), $collectionValidator); } elseif (class_exists($propertyTargetClassName) && !TypeHandlingUtility::isCoreType($propertyTargetClassName) && !in_array(SingletonInterface::class, class_implements($propertyTargetClassName) ?: [], true) ) { // class_exists($propertyTargetClassName) checks, if the type of the property is an object // instead of a simple type. Like DateTime or another model. // // !TypeHandlingUtility::isCoreType($propertyTargetClassName) checks if the type of the property // is not a core type, which are Enums and File objects for example. // @todo: check why these types shouldn't be validated. // // !in_array(SingletonInterface::class, class_implements($propertyTargetClassName, true), true) // checks if the class is an instance of a Singleton // @todo: check why Singletons shouldn't be validated. // // (Alexander Schnitzler) By looking at this code I assume that this is the path for 1:1 // relations in models. Still, the question remains why it excludes core types and singletons. // It makes sense on a theoretical level, but I don't see a technical issue allowing these as // well. $validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName, $request); if ($validatorForProperty->count() > 0) { $objectValidator->addPropertyValidator($property->getName(), $validatorForProperty); } } } foreach ($property->getValidators() as $validatorDefinition) { // @todo: At this point we already have the class name of the validator, thus there is not need // calling ValidatorClassNameResolver::resolve inside // \TYPO3\CMS\Extbase\Validation\ValidatorResolver::createValidator once again. However, to // keep things simple for now, we still use the method createValidator here. In the future, // createValidator must only accept FQCN's. if (isset($validatorDefinition['constraint'])) { $newValidator = new ConstraintDecoratingValidator($validatorDefinition['constraint']); } else { $newValidator = $this->createValidator( $validatorDefinition['className'], $validatorDefinition['options'], $request, ); } if ($newValidator === null) { throw new NoSuchValidatorException( 'Invalid #[Validate] attribute in ' . $targetClassName . '::' . $property->getName() . ': ' . 'Could not resolve class name for validator "' . $validatorDefinition['className'] . '".', 1241098027 ); } $objectValidator->addPropertyValidator($property->getName(), $newValidator); } } if (!empty($objectValidator->getPropertyValidators())) { $conjunctionValidator->addValidator($objectValidator); } } }