diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-83475-AggregateValidatorInformationInClassSchema.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-83475-AggregateValidatorInformationInClassSchema.rst new file mode 100644 index 000000000000..550ced0716f6 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-83475-AggregateValidatorInformationInClassSchema.rst @@ -0,0 +1,55 @@ +.. include:: ../../Includes.txt + +================================================================== +Breaking: #83475 - Aggregate validator information in class schema +================================================================== + +See :issue:`83475` + +Description +=========== + +It is no longer possible to use the following semantic sugar to define validators for properties of action parameters: + +.. code-block:: php + + /* + * @param Model $model + * @validate $model.property NotEmpty + */ + public function foo(Model $model){} + +Mind the dot and the reference to the property. This will no longer work. +Of course, the regular validation of action parameters stays intact. + +.. code-block:: php + + /* + * @param Model $model + * @validate $model CustomValidator + */ + public function foo(Model $model){} + +This will continue to work. + + +Impact +====== + +If you rely on that feature, you need to manually implement the validation in the future. + + +Affected Installations +====================== + +All installations that use that feature. + + +Migration +========= + +If you used that feature for adding validators to models, you can define the validators inside the model instead or inside a model validator, that is automatically registered and loaded if defined. + +When using that feature with regular objects, you need to write custom validators and call the desired property validators in there. + +.. index:: NotScanned diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83475-AggregateValidatorInformationInClassSchema-1.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83475-AggregateValidatorInformationInClassSchema-1.rst new file mode 100644 index 000000000000..a5a88b60f800 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83475-AggregateValidatorInformationInClassSchema-1.rst @@ -0,0 +1,41 @@ +.. include:: ../../Includes.txt + +===================================================================== +Deprecation: #83475 - Aggregate validator information in class schema +===================================================================== + +See :issue:`83475` + +Description +=========== + +The method `\TYPO3\CMS\Extbase\Mvc\Controller\ActionController::getActionMethodParameters` is deprecated and will be removed in TYPO3 v10.0 + + +Impact +====== + +The method is not considered public api and it is unlikely that the methods is used in the wild. If you rely on that method, please migrate your code base. + + +Affected Installations +====================== + +All installations that use that method. + + +Migration +========= + +Use the ClassSchema class and get all necessary information from it. +Example: + +.. code-block:: php + + $reflectionService = $objectManager->get(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class); + $methods = $reflectionService->getClassSchema($className)->getMethods(); + $actions = array_filter($methods, function($method){ + return $method['isAction']; + }); + +.. index:: PHP-API, FullyScanned diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83475-AggregateValidatorInformationInClassSchema-2.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83475-AggregateValidatorInformationInClassSchema-2.rst new file mode 100644 index 000000000000..180d6aebad33 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83475-AggregateValidatorInformationInClassSchema-2.rst @@ -0,0 +1,32 @@ +.. include:: ../../Includes.txt + +===================================================================== +Deprecation: #83475 - Aggregate validator information in class schema +===================================================================== + +See :issue:`83475` + +Description +=========== + +The method `\TYPO3\CMS\Extbase\Validation\ValidatorResolver::buildMethodArgumentsValidatorConjunctions` is deprecated and will be removed in TYPO3 v10.0 + + +Impact +====== + +The method is not considered public api and it is unlikely that the methods is used in the wild. If you rely on that method, you will need to implement the logic yourself. + + +Affected Installations +====================== + +All installations that use that method. + + +Migration +========= + +There is no migration + +.. index:: PHP-API, FullyScanned diff --git a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php index d5af3a1a4d03..82854de303bc 100644 --- a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php +++ b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php @@ -21,6 +21,7 @@ use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest; use TYPO3\CMS\Extbase\Validation\Validator\AbstractCompositeValidator; +use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator; use TYPO3Fluid\Fluid\View\TemplateView; /** @@ -250,26 +251,37 @@ protected function initializeActionMethodArguments() */ protected function initializeActionMethodValidators() { + $methodParameters = $this->reflectionService->getMethodParameters(static::class, $this->actionMethodName); - /** - * @todo: add validation group support - * (https://review.typo3.org/#/c/13556/4) - */ - $actionMethodParameters = static::getActionMethodParameters($this->objectManager); - if (isset($actionMethodParameters[$this->actionMethodName])) { - $methodParameters = $actionMethodParameters[$this->actionMethodName]; - } else { - $methodParameters = []; + /** @var ConjunctionValidator[] $validatorConjunctions */ + $validatorConjunctions = []; + foreach ($methodParameters as $parameterName => $methodParameter) { + /** @var ConjunctionValidator $validatorConjunction */ + $validatorConjunction = $this->objectManager->get(ConjunctionValidator::class); + + // @todo: remove check for old underscore model name syntax once it's possible + if (strpbrk($methodParameter['type'], '_\\') === false) { + // this checks if the type is a simply type and then adds a + // validator. StringValidator and such for example. + $typeValidator = $this->validatorResolver->createValidator($methodParameter['type']); + + if ($typeValidator !== null) { + $validatorConjunction->addValidator($typeValidator); + } + } + + $validatorConjunctions[$parameterName] = $validatorConjunction; + + foreach ($methodParameter['validators'] as $validator) { + $validatorConjunctions[$parameterName]->addValidator( + $this->objectManager->get($validator['className'], $validator['options']) + ); + } } - /** - * @todo: add resolving of $actionValidateAnnotations and pass them to - * buildMethodArgumentsValidatorConjunctions as in TYPO3.Flow - */ - $parameterValidators = $this->validatorResolver->buildMethodArgumentsValidatorConjunctions(static::class, $this->actionMethodName, $methodParameters); /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */ foreach ($this->arguments as $argument) { - $validator = $parameterValidators[$argument->getName()]; + $validator = $validatorConjunctions[$argument->getName()]; $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType()); if (!empty($baseValidatorConjunction) && $validator instanceof AbstractCompositeValidator) { @@ -636,9 +648,15 @@ protected function getFlattenedValidationErrorMessage() * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager * * @return array Array of method parameters by action name + * @deprecated */ public static function getActionMethodParameters($objectManager) { + trigger_error( + 'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.', + E_USER_DEPRECATED + ); + $reflectionService = $objectManager->get(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class); $result = []; diff --git a/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php b/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php index 3c7f681d808c..4eb41908c8fb 100644 --- a/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php +++ b/typo3/sysext/extbase/Classes/Reflection/ClassSchema.php @@ -17,6 +17,8 @@ use Doctrine\Common\Annotations\AnnotationReader; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\ClassNamingUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\StringUtility; use TYPO3\CMS\Extbase\Annotation\IgnoreValidation; use TYPO3\CMS\Extbase\Annotation\Inject; use TYPO3\CMS\Extbase\Annotation\ORM\Cascade; @@ -24,7 +26,11 @@ use TYPO3\CMS\Extbase\Annotation\ORM\Transient; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject; +use TYPO3\CMS\Extbase\Mvc\Controller\ControllerInterface; use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility; +use TYPO3\CMS\Extbase\Validation\Exception\InvalidTypeHintException; +use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationConfigurationException; +use TYPO3\CMS\Extbase\Validation\ValidatorResolver; /** * A class schema @@ -89,6 +95,11 @@ class ClassSchema */ private $isSingleton; + /** + * @var bool + */ + private $isController; + /** * @var array */ @@ -123,6 +134,7 @@ public function __construct($className) $reflectionClass = new \ReflectionClass($className); $this->isSingleton = $reflectionClass->implementsInterface(SingletonInterface::class); + $this->isController = $reflectionClass->implementsInterface(ControllerInterface::class); if ($reflectionClass->isSubclassOf(AbstractEntity::class)) { $this->modelType = static::MODELTYPE_ENTITY; @@ -164,7 +176,8 @@ protected function reflectProperties(\ReflectionClass $reflectionClass) 'type' => null, // Extbase 'elementType' => null, // Extbase 'annotations' => [], - 'tags' => [] + 'tags' => [], + 'validators' => [] ]; $docCommentParser = new DocCommentParser(true); @@ -179,10 +192,24 @@ protected function reflectProperties(\ReflectionClass $reflectionClass) $this->properties[$propertyName]['annotations']['type'] = null; $this->properties[$propertyName]['annotations']['cascade'] = null; $this->properties[$propertyName]['annotations']['dependency'] = null; - $this->properties[$propertyName]['annotations']['validators'] = []; if ($docCommentParser->isTaggedWith('validate')) { - $this->properties[$propertyName]['annotations']['validators'] = $docCommentParser->getTagValues('validate'); + $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class); + + $validateValues = $docCommentParser->getTagValues('validate'); + foreach ($validateValues as $validateValue) { + $validatorConfiguration = $validatorResolver->parseValidatorAnnotation($validateValue); + + foreach ($validatorConfiguration['validators'] ?? [] as $validator) { + $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validator['validatorName']); + + $this->properties[$propertyName]['validators'][] = [ + 'name' => $validator['validatorName'], + 'options' => $validator['validatorOptions'], + 'className' => $validatorObjectName, + ]; + } + } } if ($annotationReader->getPropertyAnnotation($reflectionProperty, Lazy::class) instanceof Lazy) { @@ -307,12 +334,12 @@ protected function reflectMethods(\ReflectionClass $reflectionClass) $this->methods[$methodName]['params'] = []; $this->methods[$methodName]['tags'] = []; $this->methods[$methodName]['annotations'] = []; + $this->methods[$methodName]['isAction'] = StringUtility::endsWith($methodName, 'Action'); $docCommentParser = new DocCommentParser(true); $docCommentParser->parseDocComment($reflectionMethod->getDocComment()); - $this->methods[$methodName]['annotations']['validators'] = []; - + $argumentValidators = []; foreach ($docCommentParser->getTagsValues() as $tag => $values) { if ($tag === 'ignorevalidation') { trigger_error( @@ -320,8 +347,22 @@ protected function reflectMethods(\ReflectionClass $reflectionClass) E_USER_DEPRECATED ); } - if ($tag === 'validate') { - $this->methods[$methodName]['annotations']['validators'] = $values; + if ($tag === 'validate' && $this->isController && $this->methods[$methodName]['isAction']) { + $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class); + + foreach ($values as $validate) { + $methodValidatorDefinition = $validatorResolver->parseValidatorAnnotation($validate); + + foreach ($methodValidatorDefinition['validators'] as $validator) { + $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validator['validatorName']); + + $argumentValidators[$methodValidatorDefinition['argumentName']][] = [ + 'name' => $validator['validatorName'], + 'options' => $validator['validatorOptions'], + 'className' => $validatorObjectName, + ]; + } + } } $this->methods[$methodName]['tags'][$tag] = array_map(function ($value) use ($tag) { // not stripping the dollar sign for @validate annotations is just @@ -332,6 +373,7 @@ protected function reflectMethods(\ReflectionClass $reflectionClass) return $tag === 'validate' ? $value : ltrim($value, '$'); }, $values); } + unset($methodValidatorDefinition); foreach ($annotationReader->getMethodAnnotations($reflectionMethod) as $annotation) { if ($annotation instanceof IgnoreValidation) { @@ -359,6 +401,7 @@ protected function reflectMethods(\ReflectionClass $reflectionClass) $this->methods[$methodName]['params'][$parameterName]['hasDefaultValue'] = $reflectionParameter->isDefaultValueAvailable(); $this->methods[$methodName]['params'][$parameterName]['defaultValue'] = null; // compat $this->methods[$methodName]['params'][$parameterName]['dependency'] = null; // Extbase DI + $this->methods[$methodName]['params'][$parameterName]['validators'] = []; if ($reflectionParameter->isDefaultValueAvailable()) { $this->methods[$methodName]['params'][$parameterName]['default'] = $reflectionParameter->getDefaultValue(); @@ -397,6 +440,29 @@ protected function reflectMethods(\ReflectionClass $reflectionClass) ) { $this->methods[$methodName]['params'][$parameterName]['dependency'] = $reflectionParameter->getClass()->getName(); } + + // Extbase Validation + if (isset($argumentValidators[$parameterName])) { + if ($this->methods[$methodName]['params'][$parameterName]['type'] === null) { + throw new InvalidTypeHintException( + 'Missing type information for parameter "$' . $parameterName . '" in ' . $this->className . '->' . $methodName . '(): Either use an @param annotation or use a type hint.', + 1515075192 + ); + } + + $this->methods[$methodName]['params'][$parameterName]['validators'] = $argumentValidators[$parameterName]; + unset($argumentValidators[$parameterName]); + } + } + + // Extbase Validation + foreach ($argumentValidators as $parameterName => $validators) { + $validatorNames = array_column($validators, 'name'); + + throw new InvalidValidationConfigurationException( + 'Invalid validate annotation in ' . $this->className . '->' . $methodName . '(): The following validators have been defined for missing param "$' . $parameterName . '": ' . implode(', ', $validatorNames), + 1515073585 + ); } // Extbase diff --git a/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php b/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php index 7e0d5a58e27e..c55789b6fdd2 100644 --- a/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php +++ b/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php @@ -110,6 +110,7 @@ public function createValidator($validatorType, array $validatorOptions = []) $validator = $this->objectManager->get($validatorObjectName, $validatorOptions); + // Move this check into ClassSchema if (!($validator instanceof \TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface)) { throw new Exception\NoSuchValidatorException('The validator "' . $validatorObjectName . '" does not implement TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface!', 1300694875); } @@ -152,9 +153,15 @@ public function getBaseValidatorConjunction($targetClassName) * @throws \TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationConfigurationException * @throws \TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException * @throws \TYPO3\CMS\Extbase\Validation\Exception\InvalidTypeHintException + * @deprecated */ public function buildMethodArgumentsValidatorConjunctions($className, $methodName, array $methodParameters = null, array $methodValidateAnnotations = null) { + trigger_error( + 'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.', + E_USER_DEPRECATED + ); + /** @var ConjunctionValidator[] $validatorConjunctions */ $validatorConjunctions = []; @@ -362,8 +369,9 @@ protected function addCustomValidators($targetClassName, ConjunctionValidator &$ * * @param string $validateValue * @return array + * @internal */ - protected function parseValidatorAnnotation($validateValue) + public function parseValidatorAnnotation($validateValue) { $matches = []; if ($validateValue[0] === '$') { @@ -432,8 +440,9 @@ protected function unquoteString(&$quotedValue) * * @throws Exception\NoSuchValidatorException * @return string Name of the validator object + * @internal */ - protected function resolveValidatorObjectName($validatorName) + public function resolveValidatorObjectName($validatorName) { if (strpos($validatorName, ':') !== false) { // Found shorthand validator, either extbase or foreign extension diff --git a/typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php b/typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php index 7d03fab6774e..cb26c2002f6e 100644 --- a/typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php +++ b/typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php @@ -17,6 +17,10 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Persistence\ObjectStorage; use TYPO3\CMS\Extbase\Reflection\ClassSchema; +use TYPO3\CMS\Extbase\Validation\Exception\InvalidTypeHintException; +use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationConfigurationException; +use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator; +use TYPO3\CMS\Extbase\Validation\Validator\StringLengthValidator; /** * Test case @@ -351,33 +355,127 @@ public function testClassSchemaGetTags() /** * @test */ - public function classSchemaDetectsValidateAnnotation() + public function classSchemaDetectsValidateAnnotationsModelProperties() { - $classSchema = new ClassSchema(Fixture\DummyClassWithValidateAnnotation::class); + $classSchema = new ClassSchema(Fixture\DummyModel::class); static::assertSame( [], - $classSchema->getProperty('propertyWithoutValidateAnnotations')['annotations']['validators'] + $classSchema->getProperty('propertyWithoutValidateAnnotations')['validators'] ); static::assertSame( [ - 'NotEmpty', - 'Empty (Foo=Bar)' + [ + 'name' => 'StringLength', + 'options' => [ + 'minimum' => '1', + 'maximum' => '10', + ], + 'className' => StringLengthValidator::class + ], + [ + 'name' => 'NotEmpty', + 'options' => [], + 'className' => NotEmptyValidator::class + ], + [ + 'name' => 'TYPO3.CMS.Extbase:NotEmpty', + 'options' => [], + 'className' => NotEmptyValidator::class + ], + [ + 'name' => 'TYPO3.CMS.Extbase.Tests.Unit.Reflection.Fixture:DummyValidator', + 'options' => [], + 'className' => Fixture\Validation\Validator\DummyValidator::class + ], + [ + 'name' => '\TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator', + 'options' => [], + 'className' => NotEmptyValidator::class + ], + [ + 'name' => NotEmptyValidator::class, + 'options' => [], + 'className' => NotEmptyValidator::class + ] ], - $classSchema->getProperty('propertyWithValidateAnnotations')['annotations']['validators'] + $classSchema->getProperty('propertyWithValidateAnnotations')['validators'] ); + } + + /** + * @test + */ + public function classSchemaDetectsValidateAnnotationsOfControllerActions() + { + $classSchema = new ClassSchema(Fixture\DummyController::class); static::assertSame( [], - $classSchema->getMethod('methodWithoutValidateAnnotations')['annotations']['validators'] + $classSchema->getMethod('methodWithoutValidateAnnotationsAction')['params']['fooParam']['validators'] ); static::assertSame( [ - '$fooParam FooValidator (FooValidatorOptionKey=FooValidatorOptionValue)', - '$fooParam BarValidator' + [ + 'name' => 'StringLength', + 'options' => [ + 'minimum' => '1', + 'maximum' => '10', + ], + 'className' => StringLengthValidator::class + ], + [ + 'name' => 'NotEmpty', + 'options' => [], + 'className' => NotEmptyValidator::class + ], + [ + 'name' => 'TYPO3.CMS.Extbase:NotEmpty', + 'options' => [], + 'className' => NotEmptyValidator::class + ], + [ + 'name' => 'TYPO3.CMS.Extbase.Tests.Unit.Reflection.Fixture:DummyValidator', + 'options' => [], + 'className' => Fixture\Validation\Validator\DummyValidator::class + ], + [ + 'name' => '\TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator', + 'options' => [], + 'className' => NotEmptyValidator::class + ], + [ + 'name' => NotEmptyValidator::class, + 'options' => [], + 'className' => NotEmptyValidator::class + ] ], - $classSchema->getMethod('methodWithValidateAnnotations')['annotations']['validators'] + $classSchema->getMethod('methodWithValidateAnnotationsAction')['params']['fooParam']['validators'] ); } + + /** + * @test + */ + public function classSchemaGenerationThrowsExceptionWithValidateAnnotationsForParamWithoutTypeHint() + { + $this->expectException(InvalidTypeHintException::class); + $this->expectExceptionMessage('Missing type information for parameter "$fooParam" in TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyControllerWithValidateAnnotationWithoutParamTypeHint->methodWithValidateAnnotationsAction(): Either use an @param annotation or use a type hint.'); + $this->expectExceptionCode(1515075192); + + new ClassSchema(Fixture\DummyControllerWithValidateAnnotationWithoutParamTypeHint::class); + } + + /** + * @test + */ + public function classSchemaGenerationThrowsExceptionWithValidateAnnotationsForMissingParam() + { + $this->expectException(InvalidValidationConfigurationException::class); + $this->expectExceptionMessage('Invalid validate annotation in TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyControllerWithValidateAnnotationWithoutParam->methodWithValidateAnnotationsAction(): The following validators have been defined for missing param "$fooParam": NotEmpty, StringLength'); + $this->expectExceptionCode(1515073585); + + new ClassSchema(Fixture\DummyControllerWithValidateAnnotationWithoutParam::class); + } } diff --git a/typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyController.php b/typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyController.php new file mode 100644 index 000000000000..308bdcd23ba2 --- /dev/null +++ b/typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyController.php @@ -0,0 +1,44 @@ + false, 'defaultValue' => null, 'dependency' => null, + 'validators' => [], ] ], $parameters); @@ -175,6 +176,7 @@ public function getMethodParametersWithShortTypeNames() 'hasDefaultValue' => false, 'defaultValue' => null, 'dependency' => null, + 'validators' => [], ], 'foo' => [ 'position' => 1, @@ -189,6 +191,7 @@ public function getMethodParametersWithShortTypeNames() 'hasDefaultValue' => false, 'defaultValue' => null, 'dependency' => null, + 'validators' => [], ] ], $parameters); } diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Validation/ValidatorResolverTest.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Validation/ValidatorResolverTest.php new file mode 100644 index 000000000000..fd31d32b69d7 --- /dev/null +++ b/typo3/sysext/extbase/Tests/UnitDeprecated/Validation/ValidatorResolverTest.php @@ -0,0 +1,137 @@ +getAccessibleMock(\TYPO3\CMS\Extbase\Mvc\Controller\ActionController::class, ['fooAction'], [], '', false); + $methodParameters = []; + $mockReflectionService = $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class); + $mockReflectionService->expects($this->once())->method('getMethodParameters')->with(get_class($mockController), 'fooAction')->will($this->returnValue($methodParameters)); + $validatorResolver = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Validation\ValidatorResolver::class, ['createValidator']); + $validatorResolver->_set('reflectionService', $mockReflectionService); + $result = $validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($mockController), 'fooAction'); + $this->assertSame([], $result); + } + + /** + * @test + */ + public function buildMethodArgumentsValidatorConjunctionsBuildsAConjunctionFromValidateAnnotationsOfTheSpecifiedMethod() + { + $mockObject = $this->getMockBuilder('stdClass') + ->setMethods(['fooMethod']) + ->disableOriginalConstructor() + ->getMock(); + $methodParameters = [ + 'arg1' => [ + 'type' => 'string' + ], + 'arg2' => [ + 'type' => 'array' + ] + ]; + $methodTagsValues = [ + 'param' => [ + 'string $arg1', + 'array $arg2' + ], + 'validate' => [ + '$arg1 Foo(bar = baz), Bar', + '$arg2 VENDOR\\ModelCollection\\Domain\\Model\\Model' + ] + ]; + $mockReflectionService = $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class); + $mockReflectionService->expects($this->once())->method('getMethodTagsValues')->with(get_class($mockObject), 'fooAction')->will($this->returnValue($methodTagsValues)); + $mockReflectionService->expects($this->once())->method('getMethodParameters')->with(get_class($mockObject), 'fooAction')->will($this->returnValue($methodParameters)); + $mockStringValidator = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface::class); + $mockArrayValidator = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface::class); + $mockFooValidator = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface::class); + $mockBarValidator = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface::class); + $mockQuuxValidator = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface::class); + $conjunction1 = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator::class); + $conjunction1->expects($this->at(0))->method('addValidator')->with($mockStringValidator); + $conjunction1->expects($this->at(1))->method('addValidator')->with($mockFooValidator); + $conjunction1->expects($this->at(2))->method('addValidator')->with($mockBarValidator); + $conjunction2 = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator::class); + $conjunction2->expects($this->at(0))->method('addValidator')->with($mockArrayValidator); + $conjunction2->expects($this->at(1))->method('addValidator')->with($mockQuuxValidator); + $mockArguments = new \TYPO3\CMS\Extbase\Mvc\Controller\Arguments(); + $mockArguments->addArgument(new \TYPO3\CMS\Extbase\Mvc\Controller\Argument('arg1', 'dummyValue')); + $mockArguments->addArgument(new \TYPO3\CMS\Extbase\Mvc\Controller\Argument('arg2', 'dummyValue')); + $validatorResolver = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Validation\ValidatorResolver::class, ['createValidator']); + $validatorResolver->expects($this->at(0))->method('createValidator')->with(\TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator::class)->will($this->returnValue($conjunction1)); + $validatorResolver->expects($this->at(1))->method('createValidator')->with('string')->will($this->returnValue($mockStringValidator)); + $validatorResolver->expects($this->at(2))->method('createValidator')->with(\TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator::class)->will($this->returnValue($conjunction2)); + $validatorResolver->expects($this->at(3))->method('createValidator')->with('array')->will($this->returnValue($mockArrayValidator)); + $validatorResolver->expects($this->at(4))->method('createValidator')->with('Foo', ['bar' => 'baz'])->will($this->returnValue($mockFooValidator)); + $validatorResolver->expects($this->at(5))->method('createValidator')->with('Bar')->will($this->returnValue($mockBarValidator)); + $validatorResolver->expects($this->at(6))->method('createValidator')->with('VENDOR\\ModelCollection\\Domain\\Model\\Model')->will($this->returnValue($mockQuuxValidator)); + $validatorResolver->_set('reflectionService', $mockReflectionService); + $result = $validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($mockObject), 'fooAction'); + $this->assertEquals(['arg1' => $conjunction1, 'arg2' => $conjunction2], $result); + } + + /** + * @test + */ + public function buildMethodArgumentsValidatorConjunctionsThrowsExceptionIfValidationAnnotationForNonExistingArgumentExists() + { + $this->expectException(InvalidValidationConfigurationException::class); + $this->expectExceptionCode(1253172726); + $mockObject = $this->getMockBuilder('stdClass') + ->setMethods(['fooMethod']) + ->disableOriginalConstructor() + ->getMock(); + $methodParameters = [ + 'arg1' => [ + 'type' => 'string' + ] + ]; + $methodTagsValues = [ + 'param' => [ + 'string $arg1' + ], + 'validate' => [ + '$arg2 VENDOR\\ModelCollection\\Domain\\Model\\Model' + ] + ]; + $mockReflectionService = $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class); + $mockReflectionService->expects($this->once())->method('getMethodTagsValues')->with(get_class($mockObject), 'fooAction')->will($this->returnValue($methodTagsValues)); + $mockReflectionService->expects($this->once())->method('getMethodParameters')->with(get_class($mockObject), 'fooAction')->will($this->returnValue($methodParameters)); + $mockStringValidator = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface::class); + $mockQuuxValidator = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface::class); + $conjunction1 = $this->createMock(\TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator::class); + $conjunction1->expects($this->at(0))->method('addValidator')->with($mockStringValidator); + $validatorResolver = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Validation\ValidatorResolver::class, ['createValidator']); + $validatorResolver->expects($this->at(0))->method('createValidator')->with(\TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator::class)->will($this->returnValue($conjunction1)); + $validatorResolver->expects($this->at(1))->method('createValidator')->with('string')->will($this->returnValue($mockStringValidator)); + $validatorResolver->expects($this->at(2))->method('createValidator')->with('VENDOR\\ModelCollection\\Domain\\Model\\Model')->will($this->returnValue($mockQuuxValidator)); + $validatorResolver->_set('reflectionService', $mockReflectionService); + $validatorResolver->buildMethodArgumentsValidatorConjunctions(get_class($mockObject), 'fooAction'); + } +} diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php index 405cce2d379d..94a0a74ff5e4 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php @@ -2025,4 +2025,11 @@ 'Deprecation-84407-AJAXRequestMethodsInRsaEncryptionEncoder.rst', ], ], + 'TYPO3\CMS\Extbase\Validation\ValidatorResolver->buildMethodArgumentsValidatorConjunctions' => [ + 'numberOfMandatoryArguments' => 1, + 'maximumNumberOfArguments' => 1, + 'restFiles' => [ + 'Deprecation-83475-AggregateValidatorInformationInClassSchema-1.rst', + ], + ], ]; diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php index e156b20d1b55..cec59b78e80a 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php @@ -540,4 +540,11 @@ 'Deprecation-83254-MovedPageGenerationMethodsIntoTSFE.rst', ], ], + 'TYPO3\CMS\Extbase\Mvc\Controller\ActionController::getActionMethodParameters' => [ + 'numberOfMandatoryArguments' => 2, + 'maximumNumberOfArguments' => 4, + 'restFiles' => [ + 'Deprecation-83475-AggregateValidatorInformationInClassSchema-2.rst', + ], + ], ];