diff --git a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php index fbbb959353cf6ef822bdc350be864f62aa0c8cc9..f23cb3163b0bf390761b3bf14ebf94403f7346f4 100644 --- a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php +++ b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php @@ -349,8 +349,7 @@ abstract class ActionController implements ControllerInterface $validator = $this->validatorResolver->createValidator(ConjunctionValidator::class, []); foreach ($classSchemaMethodParameter->getValidators() as $validatorDefinition) { /** @var ValidatorInterface $validatorInstance */ - $validatorInstance = GeneralUtility::makeInstance($validatorDefinition['className']); - $validatorInstance->setOptions($validatorDefinition['options']); + $validatorInstance = $this->validatorResolver->createValidator($validatorDefinition['className'], $validatorDefinition['options']); $validator->addValidator( $validatorInstance ); diff --git a/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php b/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php index f59ca586326c7bf1adfbb70834f177a5f7c8ae13..fe91257d0c14281cd6abe91f5e552c9506a2b084 100644 --- a/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php +++ b/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php @@ -50,20 +50,11 @@ class ValidatorResolver implements SingletonInterface */ public function createValidator(string $validatorType, array $validatorOptions = []): ?ValidatorInterface { - // todo: ValidatorResolver should not take care of creating validator instances. - // todo: Instead, a factory should be used. try { $validatorObjectName = ValidatorClassNameResolver::resolve($validatorType); - /** @var ValidatorInterface $validator */ $validator = GeneralUtility::makeInstance($validatorObjectName); $validator->setOptions($validatorOptions); - - // @todo: Move this check into ClassSchema - if (!($validator instanceof ValidatorInterface)) { - throw new NoSuchValidatorException('The validator "' . $validatorObjectName . '" does not implement TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface!', 1300694875); - } - return $validator; } catch (NoSuchValidatorException $e) { GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__)->debug($e->getMessage()); @@ -108,8 +99,8 @@ class ValidatorResolver implements SingletonInterface */ protected function buildBaseValidatorConjunction(string $indexKey, string $targetClassName): void { - $conjunctionValidator = new ConjunctionValidator(); - $conjunctionValidator->setOptions([]); + /** @var ConjunctionValidator $conjunctionValidator */ + $conjunctionValidator = $this->createValidator(ConjunctionValidator::class); $this->baseValidatorConjunctions[$indexKey] = $conjunctionValidator; // note: the simpleType check reduces lookups to the class loader @@ -117,8 +108,8 @@ class ValidatorResolver implements SingletonInterface $classSchema = $this->reflectionService->getClassSchema($targetClassName); // Model based validator - $objectValidator = GeneralUtility::makeInstance(GenericObjectValidator::class); - $objectValidator->setOptions([]); + /** @var GenericObjectValidator $objectValidator */ + $objectValidator = $this->createValidator(GenericObjectValidator::class); foreach ($classSchema->getProperties() as $property) { if ($property->getType() === null) { // todo: The type is only necessary here for further analyzing whether it's a simple type or @@ -134,12 +125,8 @@ class ValidatorResolver implements SingletonInterface // todo: in the ClassSchema. The information could be made available and not evaluated here again. if (!TypeHandlingUtility::isSimpleType($propertyTargetClassName)) { if (TypeHandlingUtility::isCollectionType($propertyTargetClassName)) { - $collectionValidator = $this->createValidator( - CollectionValidator::class, - [ - 'elementType' => $property->getElementType(), - ] - ); + /** @var CollectionValidator $collectionValidator */ + $collectionValidator = $this->createValidator(CollectionValidator::class, ['elementType' => $property->getElementType()]); $objectValidator->addPropertyValidator($property->getName(), $collectionValidator); } elseif (class_exists($propertyTargetClassName) && !TypeHandlingUtility::isCoreType($propertyTargetClassName) && !in_array(SingletonInterface::class, class_implements($propertyTargetClassName, true) ?: [], true)) { /* diff --git a/typo3/sysext/form/Classes/Domain/Model/Renderable/AbstractRenderable.php b/typo3/sysext/form/Classes/Domain/Model/Renderable/AbstractRenderable.php index a774dd8343066a3bbc515626767f28c8df559e54..ac73611268c9799db6c28c47ffe59cb3d426cf1c 100644 --- a/typo3/sysext/form/Classes/Domain/Model/Renderable/AbstractRenderable.php +++ b/typo3/sysext/form/Classes/Domain/Model/Renderable/AbstractRenderable.php @@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface; +use TYPO3\CMS\Extbase\Validation\ValidatorResolver; use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException; use TYPO3\CMS\Form\Domain\Model\Exception\ValidatorPresetNotFoundException; use TYPO3\CMS\Form\Domain\Model\FormDefinition; @@ -39,7 +40,6 @@ use TYPO3\CMS\Form\Domain\Model\FormDefinition; */ abstract class AbstractRenderable implements RenderableInterface, VariableRenderableInterface { - /** * Abstract "type" of this Renderable. Is used during the rendering process * to determine the template file or the View PHP class being used to render @@ -98,6 +98,8 @@ abstract class AbstractRenderable implements RenderableInterface, VariableRender */ protected $variants = []; + protected ?ValidatorResolver $validatorResolver = null; + /** * Get the type of the renderable * @@ -204,11 +206,17 @@ abstract class AbstractRenderable implements RenderableInterface, VariableRender if (isset($validatorsDefinition[$validatorIdentifier]) && is_array($validatorsDefinition[$validatorIdentifier]) && isset($validatorsDefinition[$validatorIdentifier]['implementationClassName'])) { $implementationClassName = $validatorsDefinition[$validatorIdentifier]['implementationClassName']; $defaultOptions = $validatorsDefinition[$validatorIdentifier]['options'] ?? []; - ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options); + // @todo: It would be great if Renderable's and FormElements could use DI, but especially + // FormElements which extend AbstractRenderable pollute __construct() with manual + // arguments. To retrieve the ValidatorResolver, we have to fall back to getContainer() + // for now, until this has been resolved. + if ($this->validatorResolver === null) { + $container = GeneralUtility::getContainer(); + $this->validatorResolver = $container->get(ValidatorResolver::class); + } /** @var ValidatorInterface $validator */ - $validator = GeneralUtility::makeInstance($implementationClassName); - $validator->setOptions($defaultOptions); + $validator = $this->validatorResolver->createValidator($implementationClassName, $defaultOptions); $this->addValidator($validator); return $validator; } diff --git a/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php index 7cabc595d07ae69a831112db51a4858c150c815e..77ffe3ade63c4c87fb939045b9cdb5daa6445403 100644 --- a/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php +++ b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php @@ -43,6 +43,7 @@ use TYPO3\CMS\Extbase\Property\Exception as PropertyException; use TYPO3\CMS\Extbase\Security\Cryptography\HashService; use TYPO3\CMS\Extbase\Security\Exception\InvalidArgumentForHashGenerationException; use TYPO3\CMS\Extbase\Security\Exception\InvalidHashException; +use TYPO3\CMS\Extbase\Validation\ValidatorResolver; use TYPO3\CMS\Form\Domain\Exception\RenderingException; use TYPO3\CMS\Form\Domain\Finishers\FinisherContext; use TYPO3\CMS\Form\Domain\Finishers\FinisherInterface; @@ -106,12 +107,9 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess { const HONEYPOT_NAME_SESSION_IDENTIFIER = 'tx_form_honeypot_name_'; - protected ContainerInterface $container; protected ?FormDefinition $formDefinition = null; protected ?Request $request = null; protected ResponseInterface $response; - protected HashService $hashService; - protected ConfigurationManagerInterface $configurationManager; /** * @var FormState @@ -161,13 +159,12 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess protected $currentFinisher; public function __construct( - ContainerInterface $container, - ConfigurationManagerInterface $configurationManager, - HashService $hashService + // @todo: Set readonly when FormRuntimeTest has been rewritten to a functional test + protected ContainerInterface $container, + protected readonly ConfigurationManagerInterface $configurationManager, + protected readonly HashService $hashService, + protected readonly ValidatorResolver $validatorResolver, ) { - $this->container = $container; - $this->configurationManager = $configurationManager; - $this->hashService = $hashService; $this->response = new Response(); } @@ -353,7 +350,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess $honeypotNameFromSession = $this->getHoneypotNameFromSession($this->lastDisplayedPage); if ($honeypotNameFromSession) { $honeypotElement = $this->lastDisplayedPage->createElement($honeypotNameFromSession, $renderingOptions['honeypot']['formElementToUse']); - $validator = GeneralUtility::makeInstance(EmptyValidator::class); + $validator = $this->validatorResolver->createValidator(EmptyValidator::class); $honeypotElement->addValidator($validator); } } @@ -398,7 +395,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess $referenceElement = $this->currentPage->getElements()[$randomElementNumber]; $honeypotElement = $this->currentPage->createElement($honeypotName, $renderingOptions['honeypot']['formElementToUse']); - $validator = GeneralUtility::makeInstance(EmptyValidator::class); + $validator = $this->validatorResolver->createValidator(EmptyValidator::class); $honeypotElement->addValidator($validator); if (random_int(0, 1) === 1) { diff --git a/typo3/sysext/form/Classes/Mvc/ProcessingRule.php b/typo3/sysext/form/Classes/Mvc/ProcessingRule.php index 0874be1116b41b9d9dccc333e6f429bf59d5591d..7011283b7bfe12bbacf12203137a744d0ece4cf1 100644 --- a/typo3/sysext/form/Classes/Mvc/ProcessingRule.php +++ b/typo3/sysext/form/Classes/Mvc/ProcessingRule.php @@ -23,6 +23,7 @@ use TYPO3\CMS\Extbase\Property\PropertyMapper; use TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration; use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator; use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface; +use TYPO3\CMS\Extbase\Validation\ValidatorResolver; /** * A processing Rule contains information for property mapping and validation. @@ -44,18 +45,19 @@ class ProcessingRule protected PropertyMappingConfiguration $propertyMappingConfiguration; protected ConjunctionValidator $validator; protected Result $processingMessages; - protected PropertyMapper $propertyMapper; /** * Constructs this processing rule * @internal */ - public function __construct(PropertyMapper $propertyMapper) - { - $this->propertyMapper = $propertyMapper; + public function __construct( + protected readonly PropertyMapper $propertyMapper, + ValidatorResolver $validatorResolver, + ) { $this->propertyMappingConfiguration = GeneralUtility::makeInstance(PropertyMappingConfiguration::class); - $this->validator = GeneralUtility::makeInstance(ConjunctionValidator::class); - $this->validator->setOptions([]); + /** @var ConjunctionValidator $validator */ + $validator = $validatorResolver->createValidator(ConjunctionValidator::class); + $this->validator = $validator; $this->processingMessages = GeneralUtility::makeInstance(Result::class); } diff --git a/typo3/sysext/form/Classes/Mvc/Property/PropertyMappingConfiguration.php b/typo3/sysext/form/Classes/Mvc/Property/PropertyMappingConfiguration.php index 8a1aca9e9f073dbdeb3e3942794d24f61567c5db..755f3ef1c9198643ab42c12d558647d330bf88fd 100644 --- a/typo3/sysext/form/Classes/Mvc/Property/PropertyMappingConfiguration.php +++ b/typo3/sysext/form/Classes/Mvc/Property/PropertyMappingConfiguration.php @@ -22,6 +22,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\PathUtility; use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter; use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator; +use TYPO3\CMS\Extbase\Validation\ValidatorResolver; use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload; use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface; use TYPO3\CMS\Form\Domain\Runtime\FormRuntime; @@ -35,6 +36,9 @@ use TYPO3\CMS\Form\Mvc\Validation\MimeTypeValidator; */ class PropertyMappingConfiguration implements AfterFormStateInitializedInterface { + public function __construct(protected readonly ValidatorResolver $validatorResolver) + { + } /** * This hook is called for each form element after the class @@ -74,8 +78,7 @@ class PropertyMappingConfiguration implements AfterFormStateInitializedInterface $allowedMimeTypes = array_filter($renderable->getProperties()['allowedMimeTypes']); } if (!empty($allowedMimeTypes)) { - $mimeTypeValidator = GeneralUtility::makeInstance(MimeTypeValidator::class); - $mimeTypeValidator->setOptions(['allowedMimeTypes' => $allowedMimeTypes]); + $mimeTypeValidator = $this->validatorResolver->createValidator(MimeTypeValidator::class, ['allowedMimeTypes' => $allowedMimeTypes]); $validators = [$mimeTypeValidator]; } diff --git a/typo3/sysext/form/Configuration/Services.yaml b/typo3/sysext/form/Configuration/Services.yaml index 5f1429a6613116a131dd212474d23510c7197fa1..03140deb157f9479c4a730476113328ca636ec39 100644 --- a/typo3/sysext/form/Configuration/Services.yaml +++ b/typo3/sysext/form/Configuration/Services.yaml @@ -35,6 +35,9 @@ services: public: true shared: false + TYPO3\CMS\Form\Mvc\Property\PropertyMappingConfiguration: + public: true + TYPO3\CMS\Form\Slot\ResourcePublicationSlot: tags: - name: event.listener diff --git a/typo3/sysext/form/Tests/Unit/Mvc/ProcessingRuleTest.php b/typo3/sysext/form/Tests/Unit/Mvc/ProcessingRuleTest.php index dde7285b98cca61e101ec1fc95de4da74a1d630d..7053ef2556e92a3cdb3ae082559b65121defe245 100644 --- a/typo3/sysext/form/Tests/Unit/Mvc/ProcessingRuleTest.php +++ b/typo3/sysext/form/Tests/Unit/Mvc/ProcessingRuleTest.php @@ -18,10 +18,10 @@ declare(strict_types=1); namespace TYPO3\CMS\Form\Tests\Unit\Mvc; use Prophecy\PhpUnit\ProphecyTrait; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Property\PropertyMapper; use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator; use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator; +use TYPO3\CMS\Extbase\Validation\ValidatorResolver; use TYPO3\CMS\Form\Mvc\ProcessingRule; use TYPO3\CMS\Form\Tests\Unit\Mvc\Fixtures\AnotherTestValidator; use TYPO3\CMS\Form\Tests\Unit\Mvc\Fixtures\TestValidator; @@ -41,9 +41,12 @@ class ProcessingRuleTest extends UnitTestCase { $conjunctionValidator = new ConjunctionValidator(); $conjunctionValidator->setOptions([]); - GeneralUtility::addInstance(ConjunctionValidator::class, $conjunctionValidator); - $subject = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal()); - $subject->addValidator(new TestValidator()); + $validatorResolver = $this->prophesize(ValidatorResolver::class); + $validatorResolver->createValidator(ConjunctionValidator::class)->willReturn($conjunctionValidator); + $subject = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal(), $validatorResolver->reveal()); + $testValidator = new TestValidator(); + $testValidator->setOptions([]); + $subject->addValidator($testValidator); $validators = $subject->getValidators(); $validators->rewind(); self::assertInstanceOf(AbstractValidator::class, $validators->current()); @@ -54,7 +57,11 @@ class ProcessingRuleTest extends UnitTestCase */ public function removeAllRemovesAllValidators(): void { - $subject = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal()); + $conjunctionValidator = new ConjunctionValidator(); + $conjunctionValidator->setOptions([]); + $validatorResolver = $this->prophesize(ValidatorResolver::class); + $validatorResolver->createValidator(ConjunctionValidator::class)->willReturn($conjunctionValidator); + $subject = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal(), $validatorResolver->reveal()); $subject->addValidator(new TestValidator()); $subject->addValidator(new AnotherTestValidator()); $subject->addValidator(new TestValidator()); @@ -67,7 +74,11 @@ class ProcessingRuleTest extends UnitTestCase */ public function filterValidatorRemovesValidatorsDependingOnClosure(): void { - $subject = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal()); + $conjunctionValidator = new ConjunctionValidator(); + $conjunctionValidator->setOptions([]); + $validatorResolver = $this->prophesize(ValidatorResolver::class); + $validatorResolver->createValidator(ConjunctionValidator::class)->willReturn($conjunctionValidator); + $subject = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal(), $validatorResolver->reveal()); $subject->addValidator(new TestValidator()); $subject->addValidator(new AnotherTestValidator()); $subject->addValidator(new TestValidator()); @@ -84,10 +95,13 @@ class ProcessingRuleTest extends UnitTestCase */ public function processNoPropertyMappingReturnsNotModifiedValue(): void { - GeneralUtility::addInstance(ConjunctionValidator::class, new ConjunctionValidator()); - $processingRule = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal()); + $conjunctionValidator = new ConjunctionValidator(); + $conjunctionValidator->setOptions([]); + $validatorResolver = $this->prophesize(ValidatorResolver::class); + $validatorResolver->createValidator(ConjunctionValidator::class)->willReturn($conjunctionValidator); + $subject = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal(), $validatorResolver->reveal()); $input = 'someValue'; - self::assertSame($input, $processingRule->process($input)); + self::assertSame($input, $subject->process($input)); } /** @@ -95,11 +109,14 @@ class ProcessingRuleTest extends UnitTestCase */ public function processNoPropertyMappingAndHasErrorsIfValidatorContainsErrors(): void { - GeneralUtility::addInstance(ConjunctionValidator::class, new ConjunctionValidator()); - $processingRule = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal()); - $processingRule->addValidator(new TestValidator()); + $conjunctionValidator = new ConjunctionValidator(); + $conjunctionValidator->setOptions([]); + $validatorResolver = $this->prophesize(ValidatorResolver::class); + $validatorResolver->createValidator(ConjunctionValidator::class)->willReturn($conjunctionValidator); + $subject = new ProcessingRule($this->prophesize(PropertyMapper::class)->reveal(), $validatorResolver->reveal()); + $subject->addValidator(new TestValidator()); $input = 'addError'; - $processingRule->process($input); - self::assertTrue($processingRule->getProcessingMessages()->hasErrors()); + $subject->process($input); + self::assertTrue($subject->getProcessingMessages()->hasErrors()); } } diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Property/PropertyMappingConfigurationTest.php b/typo3/sysext/form/Tests/Unit/Mvc/Property/PropertyMappingConfigurationTest.php index e1f10bc50318b81f1732d8694df2c2e71f7b8b7d..0e2e3fdfa33ea1d3caba683d78381bd7e18ed24f 100644 --- a/typo3/sysext/form/Tests/Unit/Mvc/Property/PropertyMappingConfigurationTest.php +++ b/typo3/sysext/form/Tests/Unit/Mvc/Property/PropertyMappingConfigurationTest.php @@ -17,11 +17,13 @@ declare(strict_types=1); namespace TYPO3\CMS\Form\Tests\Unit\Mvc\Property; +use Prophecy\PhpUnit\ProphecyTrait; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration as ExtbasePropertyMappingConfiguration; use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator; use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator; +use TYPO3\CMS\Extbase\Validation\ValidatorResolver; use TYPO3\CMS\Form\Domain\Model\FormDefinition; use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload; use TYPO3\CMS\Form\Mvc\ProcessingRule; @@ -35,6 +37,8 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase; */ class PropertyMappingConfigurationTest extends UnitTestCase { + use ProphecyTrait; + /** * @var bool Reset singletons created by subject */ @@ -98,8 +102,7 @@ class PropertyMappingConfigurationTest extends UnitTestCase ->method('getIdentifier') ->willReturn('foobar'); - // Property Mapping Configuration - $this->propertyMappingConfiguration = new PropertyMappingConfiguration(); + $this->propertyMappingConfiguration = new PropertyMappingConfiguration($this->prophesize(ValidatorResolver::class)->reveal()); } /** @@ -124,7 +127,9 @@ class PropertyMappingConfigurationTest extends UnitTestCase ->method('setTypeConverterOptions') ->with(UploadedFileReferenceConverter::class); - $this->propertyMappingConfiguration->afterBuildingFinished($this->fileUpload); + // Property Mapping Configuration + $propertyMappingConfiguration = new PropertyMappingConfiguration($this->prophesize(ValidatorResolver::class)->reveal()); + $propertyMappingConfiguration->afterBuildingFinished($this->fileUpload); } /** @@ -155,7 +160,12 @@ class PropertyMappingConfigurationTest extends UnitTestCase $this->assertInstanceOf(MimeTypeValidator::class, $validators[0]); }); - $this->propertyMappingConfiguration->afterBuildingFinished($this->fileUpload); + $mimeTypeValidator = new MimeTypeValidator(); + $mimeTypeValidator->setOptions(['allowedMimeTypes' => []]); + $validatorResolver = $this->prophesize(ValidatorResolver::class); + $validatorResolver->createValidator(MimeTypeValidator::class, ['allowedMimeTypes' => ['text/plain', 'application/x-www-form-urlencoded']])->willReturn($mimeTypeValidator); + $propertyMappingConfiguration = new PropertyMappingConfiguration($validatorResolver->reveal()); + $propertyMappingConfiguration->afterBuildingFinished($this->fileUpload); } /**