TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Extbase\Validation;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Reflection\ClassSchema\TypeAdapter;
|
||||
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
|
||||
use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
|
||||
use TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ConstraintDecoratingValidator;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* Validator resolver to automatically find an appropriate validator for a given subject.
|
||||
*
|
||||
* @internal only to be used within Extbase, not part of TYPO3 Core API.
|
||||
*/
|
||||
class ValidatorResolver implements SingletonInterface
|
||||
{
|
||||
protected array $baseValidatorConjunctions = [];
|
||||
|
||||
public function __construct(protected readonly ReflectionService $reflectionService) {}
|
||||
|
||||
/**
|
||||
* Get a validator for a given data type. Returns a validator implementing
|
||||
* the ValidatorInterface or NULL if no validator could be resolved.
|
||||
*
|
||||
* @param string $validatorType Either one of the built-in data types or fully qualified validator class name
|
||||
*/
|
||||
public function createValidator(
|
||||
string $validatorType,
|
||||
array $validatorOptions = [],
|
||||
?ServerRequestInterface $request = null
|
||||
): ?ValidatorInterface {
|
||||
try {
|
||||
$validatorObjectName = ValidatorClassNameResolver::resolve($validatorType);
|
||||
/** @var ValidatorInterface $validator */
|
||||
$validator = GeneralUtility::makeInstance($validatorObjectName);
|
||||
$validator->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user