[FEATURE] Replace @validate with @Extbase\Validate 70/54870/14
authorAlexander Schnitzler <git@alexanderschnitzler.de>
Tue, 8 May 2018 10:24:49 +0000 (12:24 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Mon, 14 May 2018 13:37:30 +0000 (15:37 +0200)
This patch introduces the "TYPO3\CMS\Extbase\Annotation\Validate"
annotation that replaces the @validate annotation which is
deprecated from now on.

Releases: master
Resolves: #83167
Change-Id: I9a0a3804cfb7429eaf81ec2b3ffa21c3a3d84c63
Reviewed-on: https://review.typo3.org/54870
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
33 files changed:
Build/Scripts/annotationChecker.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-83167-ReplaceValidateWithTYPO3CMSExtbaseAnnotationValidate.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-83167-ReplaceValidateWithTYPO3CMSExtbaseAnnotationValidate.rst [new file with mode: 0644]
typo3/sysext/documentation/Classes/Domain/Model/Document.php
typo3/sysext/documentation/Classes/Domain/Model/DocumentFormat.php
typo3/sysext/documentation/Classes/Domain/Model/DocumentTranslation.php
typo3/sysext/extbase/Classes/Annotation/Validate.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Domain/Model/BackendUser.php
typo3/sysext/extbase/Classes/Domain/Model/BackendUserGroup.php
typo3/sysext/extbase/Classes/Domain/Model/Category.php
typo3/sysext/extbase/Classes/Domain/Model/FileMount.php
typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
typo3/sysext/extbase/Classes/Reflection/ReflectionService.php
typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Classes/Domain/Model/Blog.php
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Classes/Domain/Model/Comment.php
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Classes/Domain/Model/Post.php
typo3/sysext/extbase/Tests/Functional/Mvc/Controller/Fixture/Controller/TestController.php
typo3/sysext/extbase/Tests/Functional/Validation/Fixture/Domain/Model/AnotherModel.php
typo3/sysext/extbase/Tests/Functional/Validation/Fixture/Domain/Model/Model.php
typo3/sysext/extbase/Tests/Functional/Validation/ValidatorResolverTest.php
typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php
typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyController.php
typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyControllerWithValidateAnnotationWithoutParam.php
typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyControllerWithValidateAnnotationWithoutParamTypeHint.php
typo3/sysext/extbase/Tests/Unit/Reflection/Fixture/DummyModel.php
typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/ClassSchemaTest.php
typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithValidateAnnotationWithoutParam.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithValidateAnnotationWithoutParamTypeHint.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithValidateAnnotations.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyModelWithValidateAnnotation.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/Validation/Validator/DummyValidator.php [new file with mode: 0644]
typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyAnnotationMatcher.php

index 1c53618..78f90c9 100755 (executable)
@@ -61,9 +61,10 @@ class NodeVisitor extends NodeVisitorAbstract
                     // PHPCheckStyle
                     'SuppressWarnings', 'noinspection',
                     // Extbase related (deprecated)
-                    'transient', 'validate', 'cli', 'flushesCaches',
+                    'transient', 'cli', 'flushesCaches',
                     // Extbase related
                     'TYPO3\\\\CMS\\\\Extbase\\\\Annotation\\\\Inject', 'Extbase\\\\Inject', 'Inject',
+                    'TYPO3\\\\CMS\\\\Extbase\\\\Annotation\\\\Validate', 'Extbase\\\\Validate', 'Validate',
                     'Transient', 'Extbase\\\\ORM\\\\Lazy', 'IgnoreValidation', 'Enum',
                     'TYPO3\\\\CMS\\\\Extbase\\\\Annotation\\\\ORM\\\\Cascade', 'Extbase\\\\ORM\\\\Cascade', 'Cascade',
                     // Extension scanner
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83167-ReplaceValidateWithTYPO3CMSExtbaseAnnotationValidate.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-83167-ReplaceValidateWithTYPO3CMSExtbaseAnnotationValidate.rst
new file mode 100644 (file)
index 0000000..1c8c374
--- /dev/null
@@ -0,0 +1,139 @@
+.. include:: ../../Includes.txt
+
+===================================================================================
+Deprecation: #83167 - Replace @validate with @TYPO3\CMS\Extbase\Annotation\Validate
+===================================================================================
+
+See :issue:`83167`
+
+Description
+===========
+
+The :php:`@validate` annotation has been marked as deprecated and should be replaced with the doctrine annotation
+:php:`@TYPO3\CMS\Extbase\Annotation\Validate`.
+
+
+Impact
+======
+
+From version 9.3 on, :php:`@validate` has been marked as deprecated and will be removed in version 10.
+
+
+Affected Installations
+======================
+
+All extensions that use :php:`@validate`
+
+
+Migration
+=========
+
+Use :php:`@TYPO3\CMS\Extbase\Annotation\Validate` instead.
+
+
+Examples:
+---------
+
+The following examples show both the old and the new way of using validation annotations. Both versions can still be used
+in the same doc block, but you should start using the new way today.
+
+.. code-block:: php
+
+       use TYPO3\CMS\Extbase\Annotation as Extbase;
+
+
+.. note::
+
+   Doctrine annotations are actual classes, so they can be either used via FQCN, imported via the use statement or even
+   be aliased which is the preferred way. As doctrine annotations can only be used in the Extbase context (for now), the
+   aliased version makes that perfectly clear even for people that are new to TYPO3.
+
+.. tip::
+
+   When using PhpStorm, you can install the `PHP Annotation` plugin that recognizes the annotation classes and makes you
+   jump directly into them. Also, it enables autocompletion for annotation options.
+
+Validators for class properties
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is how annotations look like, that register validators without options
+
+.. code-block:: php
+
+       /**
+        * @validate NotEmpty
+        * @Extbase\Validate("NotEmpty")
+        * @var Foo
+        */
+       public $property;
+
+.. code-block:: php
+
+       /**
+        * @validate NotEmpty
+        * @Extbase\Validate(validator="NotEmpty")
+        * @var Foo
+        */
+       public $property;
+
+This is how annotations look like, that register validators with options
+
+.. code-block:: php
+
+       /**
+        * @validate StringLength(minimum=3, maximum=50)
+        * @Extbase\Validate("StringLength", options={"minimum": 3, "maximum": 50})
+        * @var Foo
+        */
+       public $property;
+
+.. important::
+
+   Registering multiple validators, separated by comma, is not possible any more. Instead, use one validator per line.
+
+.. code-block:: php
+
+       /**
+        * @validate StringLength(minimum=3), StringLength(maximum=50)
+        * @Extbase\Validate("StringLength", options={"minimum": 3})
+        * @Extbase\Validate("StringLength", options={"maximum": 50})
+        * @var Foo
+        */
+       public $property;
+
+Validators for method params
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. important::
+
+   When using validators for method params, you need to define what param the validator is registered for. Also, please
+   note that the param name does no longer include the dollar sign.
+
+.. code-block:: php
+
+       /**
+        * @validate $bar NotEmpty
+        * @Extbase\Validate("NotEmpty", param="bar")
+        * @var string $foo
+        * @var string $bar
+        */
+       public function method(string $foo, string $bar)
+       {
+       }
+
+Full qualified validator class names and aliases
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Of course it's still possible to reference validators by extension key, aliases and FQCN's.
+
+.. code-block:: php
+
+       /**
+        * @Extbase\Validate("NotEmpty")
+        * @Extbase\Validate("TYPO3.CMS.Extbase:NotEmpty")
+        * @Extbase\Validate("TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator")
+        * @Extbase\Validate("\TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator")
+        */
+       protected $property;
+
+.. index:: PHP-API, ext:extbase, FullyScanned
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-83167-ReplaceValidateWithTYPO3CMSExtbaseAnnotationValidate.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-83167-ReplaceValidateWithTYPO3CMSExtbaseAnnotationValidate.rst
new file mode 100644 (file)
index 0000000..ff7df60
--- /dev/null
@@ -0,0 +1,71 @@
+.. include:: ../../Includes.txt
+
+===============================================================================
+Feature: #83167 - Replace @validate with @TYPO3\CMS\Extbase\Annotation\Validate
+===============================================================================
+
+See :issue:`83167`
+
+Description
+===========
+
+As a successor to the :php:`@validate` annotation, the doctrine annotation
+:php:`@TYPO3\CMS\Extbase\Annotation\Validate` has been introduced.
+
+
+Example:
+--------
+
+.. code-block:: php
+
+       /**
+        * @TYPO3\CMS\Extbase\Annotation\Validate
+        * @var Foo
+        */
+       public $property;
+
+Doctrine annotations are actual defined classes, therefore you can also use the annotation with a use statement.
+
+
+Example:
+--------
+
+.. code-block:: php
+
+       use TYPO3\CMS\Extbase\Annotation\Validate;
+
+.. code-block:: php
+
+       /**
+        * @Validate
+        * @var Foo
+        */
+       public $property;
+
+Used annotations can also be aliased which the core will most likely be using a lot in the future.
+
+
+Example:
+--------
+
+.. code-block:: php
+
+       use TYPO3\CMS\Extbase\Annotation as Extbase;
+
+.. code-block:: php
+
+       /**
+        * @Extbase\Validate
+        * @var Foo
+        */
+       public $property;
+
+
+Impact
+======
+
+In 9.x there is no actual impact. Both the simple :php:`@inject` and
+:php:`@TYPO3\CMS\Extbase\Annotation\Inject` can be used side by side.
+However, :php:`@inject` is deprecated in 9.x and will be removed in version 10.
+
+.. index:: PHP-API, ext:extbase, FullyScanned
index 6a830f1..723304c 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Documentation\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
+
 /**
  * An extension helper model to be used in ext:documentation context
  */
@@ -23,7 +25,7 @@ class Document extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * packageKey
      *
      * @var string
-     * @validate NotEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $packageKey;
 
@@ -31,7 +33,7 @@ class Document extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * extensionKey
      *
      * @var string
-     * @validate NotEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $extensionKey;
 
index 6306c38..fbfeb1f 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Documentation\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
+
 /**
  * An extension helper model to be used in ext:documentation context
  */
@@ -23,7 +25,7 @@ class DocumentFormat extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * format
      *
      * @var string
-     * @validate NotEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $format;
 
@@ -31,7 +33,7 @@ class DocumentFormat extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * path
      *
      * @var string
-     * @validate NotEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $path;
 
index 4497022..3989e74 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Documentation\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
+
 /**
  * An extension helper model to be used in ext:documentation context
  */
@@ -24,7 +26,7 @@ class DocumentTranslation extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * 2 char language identifier (or "" for default)
      *
      * @var string
-     * @validate NotEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $language;
 
diff --git a/typo3/sysext/extbase/Classes/Annotation/Validate.php b/typo3/sysext/extbase/Classes/Annotation/Validate.php
new file mode 100644 (file)
index 0000000..a162011
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Annotation;
+
+/*
+ * 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!
+ */
+
+use Doctrine\Common\Annotations\Annotation\Required;
+
+/**
+ * @Annotation
+ * @Target({"PROPERTY", "METHOD"})
+ */
+class Validate
+{
+    /**
+     * @var string
+     * @Required
+     */
+    public $validator = '';
+
+    /**
+     * @var string
+     */
+    public $param = '';
+
+    /**
+     * @var array
+     */
+    public $options = [];
+
+    /**
+     * @param array $values
+     */
+    public function __construct(array $values)
+    {
+        if (isset($values['value'])) {
+            $this->validator = $values['value'];
+        }
+
+        if (isset($values['validator'])) {
+            $this->validator = $values['validator'];
+        }
+
+        if (isset($values['options'])) {
+            $this->options = $values['options'];
+        }
+
+        if (isset($values['param'])) {
+            $this->param = $values['param'];
+        }
+    }
+
+    /**
+     * @return string
+     */
+    public function __toString()
+    {
+        $strings = [];
+
+        if ($this->param !== '') {
+            $strings[] = $this->param;
+        }
+
+        $strings[] = $this->validator;
+
+        if (count($this->options) > 0) {
+            $validatorOptionsStrings = [];
+            foreach ($this->options as $optionKey => $optionValue) {
+                $validatorOptionsStrings[] = $optionKey . '=' . $optionValue;
+            }
+
+            $strings[] = '(' . implode(', ', $validatorOptionsStrings) . ')';
+        }
+
+        return trim(implode(' ', $strings));
+    }
+}
index 540f75b..bf9589e 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extbase\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
+
 /**
  * This model represents a back-end user.
  *
@@ -23,7 +25,7 @@ class BackendUser extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
 {
     /**
      * @var string
-     * @validate notEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $userName = '';
 
index e0c7d27..4899c36 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extbase\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
+
 /**
  * This model represents a backend usergroup.
  *
@@ -28,7 +30,7 @@ class BackendUserGroup extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
 
     /**
      * @var string
-     * @validate notEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $title = '';
 
index 4f3f450..568909b 100644 (file)
@@ -25,7 +25,7 @@ class Category extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
 {
     /**
      * @var string
-     * @validate notEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $title = '';
 
index 0ac47c0..08f4fe1 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extbase\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
+
 /**
  * This model represents a file mount.
  *
@@ -25,7 +27,7 @@ class FileMount extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * Title of the file mount.
      *
      * @var string
-     * @validate notEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $title = '';
 
@@ -40,7 +42,7 @@ class FileMount extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * Path of the file mount.
      *
      * @var string
-     * @validate notEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $path = '';
 
index 4eb4190..b08de3d 100644 (file)
@@ -24,6 +24,7 @@ use TYPO3\CMS\Extbase\Annotation\Inject;
 use TYPO3\CMS\Extbase\Annotation\ORM\Cascade;
 use TYPO3\CMS\Extbase\Annotation\ORM\Lazy;
 use TYPO3\CMS\Extbase\Annotation\ORM\Transient;
+use TYPO3\CMS\Extbase\Annotation\Validate;
 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
 use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject;
 use TYPO3\CMS\Extbase\Mvc\Controller\ControllerInterface;
@@ -193,7 +194,33 @@ class ClassSchema
             $this->properties[$propertyName]['annotations']['cascade'] = null;
             $this->properties[$propertyName]['annotations']['dependency'] = null;
 
+            $annotations = $annotationReader->getPropertyAnnotations($reflectionProperty);
+
+            /** @var array|Validate[] $validateAnnotations */
+            $validateAnnotations = array_filter($annotations, function ($annotation) {
+                return $annotation instanceof Validate;
+            });
+
+            if (count($validateAnnotations) > 0) {
+                $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
+
+                foreach ($validateAnnotations as $validateAnnotation) {
+                    $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validateAnnotation->validator);
+
+                    $this->properties[$propertyName]['validators'][] = [
+                        'name' => $validateAnnotation->validator,
+                        'options' => $validateAnnotation->options,
+                        'className' => $validatorObjectName,
+                    ];
+                }
+            }
+
             if ($docCommentParser->isTaggedWith('validate')) {
+                trigger_error(
+                    'Tagging properties with @validate is deprecated and will be removed in TYPO3 v10.0.',
+                    E_USER_DEPRECATED
+                );
+
                 $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
 
                 $validateValues = $docCommentParser->getTagValues('validate');
@@ -340,6 +367,29 @@ class ClassSchema
             $docCommentParser->parseDocComment($reflectionMethod->getDocComment());
 
             $argumentValidators = [];
+
+            $annotations = $annotationReader->getMethodAnnotations($reflectionMethod);
+
+            /** @var array|Validate[] $validateAnnotations */
+            $validateAnnotations = array_filter($annotations, function ($annotation) {
+                return $annotation instanceof Validate;
+            });
+
+            if ($this->isController && $this->methods[$methodName]['isAction'] && count($validateAnnotations) > 0) {
+                $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
+
+                foreach ($validateAnnotations as $validateAnnotation) {
+                    $validatorName = $validateAnnotation->validator;
+                    $validatorObjectName = $validatorResolver->resolveValidatorObjectName($validatorName);
+
+                    $argumentValidators[$validateAnnotation->param][] = [
+                        'name' => $validatorName,
+                        'options' => $validateAnnotation->options,
+                        'className' => $validatorObjectName,
+                    ];
+                }
+            }
+
             foreach ($docCommentParser->getTagsValues() as $tag => $values) {
                 if ($tag === 'ignorevalidation') {
                     trigger_error(
@@ -348,6 +398,11 @@ class ClassSchema
                     );
                 }
                 if ($tag === 'validate' && $this->isController && $this->methods[$methodName]['isAction']) {
+                    trigger_error(
+                        'Tagging methods with @validate is deprecated and will be removed in TYPO3 v10.0.',
+                        E_USER_DEPRECATED
+                    );
+
                     $validatorResolver = GeneralUtility::makeInstance(ValidatorResolver::class);
 
                     foreach ($values as $validate) {
@@ -375,7 +430,7 @@ class ClassSchema
             }
             unset($methodValidatorDefinition);
 
-            foreach ($annotationReader->getMethodAnnotations($reflectionMethod) as $annotation) {
+            foreach ($annotations as $annotation) {
                 if ($annotation instanceof IgnoreValidation) {
                     $this->methods[$methodName]['tags']['ignorevalidation'][] = $annotation->argumentName;
                 }
index b6ecbfd..10d43d5 100644 (file)
@@ -138,7 +138,6 @@ class ReflectionService implements SingletonInterface
      * @param mixed $classNameOrObject The class name or an object
      * @return ClassSchema
      * @throws \TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException
-     * @throws \ReflectionException
      */
     public function getClassSchema($classNameOrObject): ClassSchema
     {
index c55789b..7af8e5e 100644 (file)
@@ -277,25 +277,30 @@ class ValidatorResolver implements \TYPO3\CMS\Core\SingletonInterface
 
         // note: the simpleType check reduces lookups to the class loader
         if (!TypeHandlingUtility::isSimpleType($targetClassName) && class_exists($targetClassName)) {
+            $classSchema = $this->reflectionService->getClassSchema($targetClassName);
+
             // Model based validator
             /** @var \TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator $objectValidator */
             $objectValidator = $this->objectManager->get(\TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator::class, []);
-            foreach ($this->reflectionService->getClassPropertyNames($targetClassName) as $classPropertyName) {
-                $classPropertyTagsValues = $this->reflectionService->getPropertyTagsValues($targetClassName, $classPropertyName);
+            foreach ($classSchema->getProperties() as $classPropertyName => $classPropertyDefinition) {
+                /** @var array|array[] $classPropertyDefinition */
+                $classPropertyTagsValues = $classPropertyDefinition['tags'];
 
                 if (!isset($classPropertyTagsValues['var'])) {
                     throw new \InvalidArgumentException(sprintf('There is no @var annotation for property "%s" in class "%s".', $classPropertyName, $targetClassName), 1363778104);
                 }
-                try {
-                    $parsedType = TypeHandlingUtility::parseType(trim(implode('', $classPropertyTagsValues['var']), ' \\'));
-                } catch (\TYPO3\CMS\Extbase\Utility\Exception\InvalidTypeException $exception) {
-                    throw new \InvalidArgumentException(sprintf(' @var annotation of ' . $exception->getMessage(), 'class "' . $targetClassName . '", property "' . $classPropertyName . '"'), 1315564744, $exception);
-                }
-                $propertyTargetClassName = $parsedType['type'];
+
+                $propertyTargetClassName = $classPropertyDefinition['type'];
                 // note: the outer simpleType check reduces lookups to the class loader
                 if (!TypeHandlingUtility::isSimpleType($propertyTargetClassName)) {
                     if (TypeHandlingUtility::isCollectionType($propertyTargetClassName)) {
-                        $collectionValidator = $this->createValidator(\TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator::class, ['elementType' => $parsedType['elementType'], 'validationGroups' => $validationGroups]);
+                        $collectionValidator = $this->createValidator(
+                            \TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator::class,
+                            [
+                                'elementType' => $classPropertyDefinition['elementType'],
+                                'validationGroups' => $validationGroups
+                            ]
+                        );
                         $objectValidator->addPropertyValidator($classPropertyName, $collectionValidator);
                     } elseif (class_exists($propertyTargetClassName) && !TypeHandlingUtility::isCoreType($propertyTargetClassName) && $this->objectManager->isRegistered($propertyTargetClassName) && $this->objectManager->getScope($propertyTargetClassName) === \TYPO3\CMS\Extbase\Object\Container\Container::SCOPE_PROTOTYPE) {
                         $validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName);
@@ -305,25 +310,17 @@ class ValidatorResolver implements \TYPO3\CMS\Core\SingletonInterface
                     }
                 }
 
-                $validateAnnotations = [];
-                // @todo: Resolve annotations via reflectionService once its available
-                if (isset($classPropertyTagsValues['validate']) && is_array($classPropertyTagsValues['validate'])) {
-                    foreach ($classPropertyTagsValues['validate'] as $validateValue) {
-                        $parsedAnnotations = $this->parseValidatorAnnotation($validateValue);
-
-                        foreach ($parsedAnnotations['validators'] as $validator) {
-                            $validateAnnotation = $validator;
-                            $validateAnnotation['argumentName'] = $parsedAnnotations['argumentName'] ?? null;
-                            $validateAnnotations[] = $validateAnnotation;
-                        }
-                    }
-                }
-
-                foreach ($validateAnnotations as $validateAnnotation) {
+                foreach ($classPropertyDefinition['validators'] as $validatorDefinition) {
                     // @todo: Respect validationGroups
-                    $newValidator = $this->createValidator($validateAnnotation['validatorName'], $validateAnnotation['validatorOptions']);
+
+                    // @todo: At this point we already have the class name of the validator, thus there is not need
+                    // @todo: calling \TYPO3\CMS\Extbase\Validation\ValidatorResolver::resolveValidatorObjectName inside
+                    // @todo: \TYPO3\CMS\Extbase\Validation\ValidatorResolver::createValidator once again. However, to
+                    // @todo: keep things simple for now, we still use the method createValidator here. In the future,
+                    // @todo: createValidator must only accept FQCN's.
+                    $newValidator = $this->createValidator($validatorDefinition['className'], $validatorDefinition['options']);
                     if ($newValidator === null) {
-                        throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $targetClassName . '::' . $classPropertyName . ': Could not resolve class name for  validator "' . $validateAnnotation->type . '".', 1241098027);
+                        throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $targetClassName . '::' . $classPropertyName . ': Could not resolve class name for validator "' . $validatorDefinition['className'] . '".', 1241098027);
                     }
                     $objectValidator->addPropertyValidator($classPropertyName, $newValidator);
                 }
@@ -349,7 +346,6 @@ class ValidatorResolver implements \TYPO3\CMS\Core\SingletonInterface
      *
      * @param string $targetClassName
      * @param ConjunctionValidator $conjunctionValidator
-     * @return Validator\ObjectValidatorInterface|null
      */
     protected function addCustomValidators($targetClassName, ConjunctionValidator &$conjunctionValidator)
     {
@@ -370,6 +366,7 @@ class ValidatorResolver implements \TYPO3\CMS\Core\SingletonInterface
      * @param string $validateValue
      * @return array
      * @internal
+     * @deprecated Helper, remove in v10 together with the deprecated consumer methods
      */
     public function parseValidatorAnnotation($validateValue)
     {
@@ -530,6 +527,7 @@ class ValidatorResolver implements \TYPO3\CMS\Core\SingletonInterface
      * @param string $methodName
      *
      * @return array
+     * @deprecated Helper, remove in v10 together with buildMethodArgumentsValidatorConjunctions()
      */
     public function getMethodValidateAnnotations($className, $methodName)
     {
index bdc99ce..ab5351a 100644 (file)
@@ -25,7 +25,7 @@ class Blog extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * The blog's title.
      *
      * @var string
-     * @validate StringLength(minimum = 1, maximum = 80)
+     * @Extbase\Validate("StringLength", options={"minimum": 1, "maximum": 80})
      */
     protected $title = '';
 
@@ -40,7 +40,7 @@ class Blog extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
      * A short description of the blog
      *
      * @var string
-     * @validate StringLength(maximum = 150)
+     * @Extbase\Validate("StringLength", options={"maximum": 150})
      */
     protected $description = '';
 
index bb38752..695ed22 100644 (file)
@@ -14,6 +14,8 @@ namespace ExtbaseTeam\BlogExample\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
+
 /**
  * A blog post comment
  */
@@ -26,19 +28,19 @@ class Comment extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
 
     /**
      * @var string
-     * @validate NotEmpty
+     * Extbase\Validate("NotEmpty")
      */
     protected $author = '';
 
     /**
      * @var string
-     * @validate EmailAddress
+     * @Extbase\Validate("EmailAddress")
      */
     protected $email = '';
 
     /**
      * @var string
-     * @validate StringLength(maximum = 500)
+     * @Extbase\Validate("StringLength", options={"maximum": 500})
      */
     protected $content = '';
 
index d625f1f..a3aa8c6 100644 (file)
@@ -28,7 +28,7 @@ class Post extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
 
     /**
      * @var string
-     * @validate StringLength(minimum = 3, maximum = 50)
+     * @Extbase\Validate("StringLength", options={"minimum": 3, "maximum": 50})
      */
     protected $title = '';
 
@@ -49,7 +49,7 @@ class Post extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
 
     /**
      * @var string
-     * @validate StringLength(minimum = 3)
+     * @Extbase\Validate("StringLength", options={"minimum": 3})
      */
     protected $content = '';
 
index 8709962..84e5671 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller\Fixture\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
 use TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfiguration;
 use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;
@@ -49,7 +50,7 @@ class TestController extends ActionController
 
     /**
      * @param string $barParam
-     * @validate $barParam TYPO3.CMS.Extbase.Tests.Functional.Mvc.Controller.Fixture:CustomValidator
+     * @Extbase\Validate("TYPO3.CMS.Extbase.Tests.Functional.Mvc.Controller.Fixture:CustomValidator", param="barParam")
      * @return string
      */
     public function barAction(string $barParam)
@@ -60,7 +61,7 @@ class TestController extends ActionController
 
     /**
      * @param array $bazParam
-     * @validate $bazParam NotEmpty
+     * @Extbase\Validate("NotEmpty", param="bazParam")
      * @return string
      */
     public function bazAction(array $bazParam)
index b2b721c..e8615e8 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Validation\Fixture\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
 
 /**
@@ -24,7 +25,7 @@ class AnotherModel extends AbstractEntity
 {
     /**
      * @var string
-     * @validate NotEmpty
+     * @Extbase\Validate("NotEmpty")
      */
     protected $foo;
 }
index 13134c9..44c1cce 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Validation\Fixture\Domain\Model;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
 
 /**
@@ -24,14 +25,15 @@ class Model extends AbstractEntity
 {
     /**
      * @var string
-     * @validate StringLength(minimum=1), StringLength(maximum=10)
-     * @validate NotEmpty
+     * @Extbase\Validate("StringLength", options={"minimum": 1})
+     * @Extbase\Validate("StringLength", options={"maximum": 10})
+     * @Extbase\Validate("NotEmpty")
      */
     protected $foo;
 
     /**
      * @var int
-     * @validate \TYPO3\CMS\Extbase\Tests\Functional\Validation\Fixture\Validation\Validator\CustomValidator
+     * @Extbase\Validate("\TYPO3\CMS\Extbase\Tests\Functional\Validation\Fixture\Validation\Validator\CustomValidator")
      */
     protected $bar;
 
index 1f37aab..b4d201a 100644 (file)
@@ -203,13 +203,13 @@ class ValidatorResolverTest extends \TYPO3\TestingFramework\Core\Functional\Func
         /** @var StringLengthValidator $propertyValidator */
         $propertyValidator = $fooPropertyValidators->current();
         static::assertInstanceOf(StringLengthValidator::class, $propertyValidator);
-        static::assertSame(['minimum' => '1', 'maximum' => PHP_INT_MAX], $propertyValidator->getOptions());
+        static::assertSame(['minimum' => 1, 'maximum' => PHP_INT_MAX], $propertyValidator->getOptions());
 
         $fooPropertyValidators->next();
         /** @var StringLengthValidator $propertyValidator */
         $propertyValidator = $fooPropertyValidators->current();
         static::assertInstanceOf(StringLengthValidator::class, $propertyValidator);
-        static::assertSame(['minimum' => 0, 'maximum' => '10'], $propertyValidator->getOptions());
+        static::assertSame(['minimum' => 0, 'maximum' => 10], $propertyValidator->getOptions());
 
         $fooPropertyValidators->next();
         $propertyValidator = $fooPropertyValidators->current();
index cb26c20..7adfa1f 100644 (file)
@@ -355,21 +355,17 @@ class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
     /**
      * @test
      */
-    public function classSchemaDetectsValidateAnnotationsModelProperties()
+    public function classSchemaDetectsValidateAnnotationsModelProperties(): void
     {
         $classSchema = new ClassSchema(Fixture\DummyModel::class);
 
         static::assertSame(
-            [],
-            $classSchema->getProperty('propertyWithoutValidateAnnotations')['validators']
-        );
-        static::assertSame(
             [
                 [
                     'name' => 'StringLength',
                     'options' => [
-                        'minimum' => '1',
-                        'maximum' => '10',
+                        'minimum' => 1,
+                        'maximum' => 10,
                     ],
                     'className' => StringLengthValidator::class
                 ],
@@ -406,22 +402,17 @@ class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
     /**
      * @test
      */
-    public function classSchemaDetectsValidateAnnotationsOfControllerActions()
+    public function classSchemaDetectsValidateAnnotationsOfControllerActions(): void
     {
         $classSchema = new ClassSchema(Fixture\DummyController::class);
 
         static::assertSame(
-            [],
-            $classSchema->getMethod('methodWithoutValidateAnnotationsAction')['params']['fooParam']['validators']
-        );
-
-        static::assertSame(
             [
                 [
                     'name' => 'StringLength',
                     'options' => [
-                        'minimum' => '1',
-                        'maximum' => '10',
+                        'minimum' => 1,
+                        'maximum' => 10,
                     ],
                     'className' => StringLengthValidator::class
                 ],
@@ -458,7 +449,7 @@ class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
     /**
      * @test
      */
-    public function classSchemaGenerationThrowsExceptionWithValidateAnnotationsForParamWithoutTypeHint()
+    public function classSchemaGenerationThrowsExceptionWithValidateDoctrineAnnotationsForParamWithoutTypeHint()
     {
         $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.');
@@ -470,7 +461,7 @@ class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
     /**
      * @test
      */
-    public function classSchemaGenerationThrowsExceptionWithValidateAnnotationsForMissingParam()
+    public function classSchemaGenerationThrowsExceptionWithValidateDoctrineAnnotationsForMissingParam()
     {
         $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');
index f5077c7..82c80b2 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
 
 /**
@@ -25,20 +26,20 @@ class DummyController extends ActionController
     /**
      * @param $fooParam
      */
-    public function methodWithoutValidateAnnotationsAction($fooParam)
+    public function methodWithoutValidateAnnotationsAction($fooParam): void
     {
     }
 
     /**
      * @param string $fooParam
-     * @validate $fooParam StringLength (minimum=1,maximum=10)
-     * @validate $fooParam NotEmpty
-     * @validate $fooParam TYPO3.CMS.Extbase:NotEmpty
-     * @validate $fooParam TYPO3.CMS.Extbase.Tests.Unit.Reflection.Fixture:DummyValidator
-     * @validate $fooParam \TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator
-     * @validate $fooParam TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator
+     * @Extbase\Validate(param="fooParam", validator="StringLength", options={"minimum": 1, "maximum": 10})
+     * @Extbase\Validate(param="fooParam", validator="NotEmpty")
+     * @Extbase\Validate(param="fooParam", validator="TYPO3.CMS.Extbase:NotEmpty")
+     * @Extbase\Validate(param="fooParam", validator="TYPO3.CMS.Extbase.Tests.Unit.Reflection.Fixture:DummyValidator")
+     * @Extbase\Validate(param="fooParam", validator="\TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator")
+     * @Extbase\Validate(param="fooParam", validator="TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator")
      */
-    public function methodWithValidateAnnotationsAction($fooParam)
+    public function methodWithValidateAnnotationsAction($fooParam): void
     {
     }
 }
index 883af39..b1b4eaf 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 declare(strict_types = 1);
+
 namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
 
 /*
@@ -15,6 +16,7 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
 
 /**
@@ -23,10 +25,10 @@ use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
 class DummyControllerWithValidateAnnotationWithoutParam extends ActionController
 {
     /**
-     * @validate $fooParam NotEmpty
-     * @validate $fooParam StringLength
+     * @Extbase\Validate(param="fooParam", validator="NotEmpty")
+     * @Extbase\Validate(param="fooParam", validator="StringLength")
      */
-    public function methodWithValidateAnnotationsAction()
+    public function methodWithValidateAnnotationsAction(): void
     {
     }
 }
index 1631fd4..1370cac 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+declare(strict_types = 1);
+
 namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
 
 /*
@@ -14,6 +16,7 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
 
 /**
@@ -22,9 +25,9 @@ use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
 class DummyControllerWithValidateAnnotationWithoutParamTypeHint extends ActionController
 {
     /**
-     * @validate $fooParam NotEmpty
+     * @Extbase\Validate(param="fooParam", validator="NotEmpty")
      */
-    public function methodWithValidateAnnotationsAction($fooParam)
+    public function methodWithValidateAnnotationsAction($fooParam): void
     {
     }
 }
index 3db902b..d15ea41 100644 (file)
@@ -1,4 +1,6 @@
 <?php
+declare(strict_types = 1);
+
 namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
 
 /*
@@ -14,6 +16,7 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Annotation as Extbase;
 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
 
 /**
@@ -24,12 +27,12 @@ class DummyModel extends AbstractEntity
     protected $propertyWithoutValidateAnnotations;
 
     /**
-     * @validate StringLength (minimum=1,maximum=10)
-     * @validate NotEmpty
-     * @validate TYPO3.CMS.Extbase:NotEmpty
-     * @validate TYPO3.CMS.Extbase.Tests.Unit.Reflection.Fixture:DummyValidator
-     * @validate \TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator
-     * @validate TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator
+     * @Extbase\Validate("StringLength", options={"minimum": 1, "maximum": 10})
+     * @Extbase\Validate("NotEmpty")
+     * @Extbase\Validate("TYPO3.CMS.Extbase:NotEmpty")
+     * @Extbase\Validate("TYPO3.CMS.Extbase.Tests.Unit.Reflection.Fixture:DummyValidator")
+     * @Extbase\Validate("\TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator")
+     * @Extbase\Validate("TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator")
      */
     protected $propertyWithValidateAnnotations;
 }
index 92be1f1..f363774 100644 (file)
@@ -15,6 +15,10 @@ namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection;
  */
 
 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
@@ -60,4 +64,122 @@ class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $classSchema = new ClassSchema(Fixture\DummyControllerWithIgnorevalidationAnnotation::class);
         static::assertTrue(isset($classSchema->getMethod('someAction')['tags']['ignorevalidation']));
     }
+
+    /**
+     * @test
+     */
+    public function classSchemaDetectsValidateAnnotationsModelProperties(): void
+    {
+        $classSchema = new ClassSchema(Fixture\DummyModelWithValidateAnnotation::class);
+
+        static::assertSame(
+            [
+                [
+                    '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.UnitDeprecated.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')['validators']
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function classSchemaDetectsValidateAnnotationsOfControllerActions(): void
+    {
+        $classSchema = new ClassSchema(Fixture\DummyControllerWithValidateAnnotations::class);
+
+        static::assertSame(
+            [
+                [
+                    '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.UnitDeprecated.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('methodWithValidateAnnotationsAction')['params']['fooParam']['validators']
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function classSchemaGenerationThrowsExceptionWithValidateAnnotationsForParamWithoutTypeHint(): void
+    {
+        $this->expectException(InvalidTypeHintException::class);
+        $this->expectExceptionMessage('Missing type information for parameter "$fooParam" in TYPO3\CMS\Extbase\Tests\UnitDeprecated\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(): void
+    {
+        $this->expectException(InvalidValidationConfigurationException::class);
+        $this->expectExceptionMessage('Invalid validate annotation in TYPO3\CMS\Extbase\Tests\UnitDeprecated\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/UnitDeprecated/Reflection/Fixture/DummyControllerWithValidateAnnotationWithoutParam.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithValidateAnnotationWithoutParam.php
new file mode 100644 (file)
index 0000000..485f65c
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
+
+/**
+ * Fixture class with @validate annotations
+ */
+class DummyControllerWithValidateAnnotationWithoutParam extends ActionController
+{
+    /**
+     * @validate $fooParam NotEmpty
+     * @validate $fooParam StringLength
+     */
+    public function methodWithValidateAnnotationsAction(): void
+    {
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithValidateAnnotationWithoutParamTypeHint.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithValidateAnnotationWithoutParamTypeHint.php
new file mode 100644 (file)
index 0000000..30d49df
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
+
+/**
+ * Fixture class with @validate annotations
+ */
+class DummyControllerWithValidateAnnotationWithoutParamTypeHint extends ActionController
+{
+    /**
+     * @validate $fooParam NotEmpty
+     */
+    public function methodWithValidateAnnotationsAction($fooParam): void
+    {
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithValidateAnnotations.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyControllerWithValidateAnnotations.php
new file mode 100644 (file)
index 0000000..a53c417
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
+
+/**
+ * Fixture class with @validate annotations
+ */
+class DummyControllerWithValidateAnnotations extends ActionController
+{
+    /**
+     * @param string $fooParam
+     * @validate $fooParam StringLength (minimum=1,maximum=10)
+     * @validate $fooParam NotEmpty
+     * @validate $fooParam TYPO3.CMS.Extbase:NotEmpty
+     * @validate $fooParam TYPO3.CMS.Extbase.Tests.UnitDeprecated.Reflection.Fixture:DummyValidator
+     * @validate $fooParam \TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator
+     * @validate $fooParam TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator
+     */
+    public function methodWithValidateAnnotationsAction($fooParam)
+    {
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyModelWithValidateAnnotation.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/DummyModelWithValidateAnnotation.php
new file mode 100644 (file)
index 0000000..c0e50d3
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
+
+/**
+ * Fixture model
+ */
+class DummyModelWithValidateAnnotation extends AbstractEntity
+{
+    /**
+     * @validate StringLength (minimum=1,maximum=10)
+     * @validate NotEmpty
+     * @validate TYPO3.CMS.Extbase:NotEmpty
+     * @validate TYPO3.CMS.Extbase.Tests.UnitDeprecated.Reflection.Fixture:DummyValidator
+     * @validate \TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator
+     * @validate TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator
+     */
+    protected $propertyWithValidateAnnotations;
+}
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/Validation/Validator/DummyValidator.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Reflection/Fixture/Validation/Validator/DummyValidator.php
new file mode 100644 (file)
index 0000000..578d3ae
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\Reflection\Fixture\Validation\Validator;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
+
+/**
+ * Fixture validator
+ */
+class DummyValidator implements ValidatorInterface
+{
+    /**
+     * @param mixed $value The value that should be validated
+     * @return \TYPO3\CMS\Extbase\Error\Result
+     */
+    public function validate($value)
+    {
+        return new \TYPO3\CMS\Extbase\Error\Result;
+    }
+
+    /**
+     * @return array
+     */
+    public function getOptions()
+    {
+        return [];
+    }
+}
index 947c247..0e50b64 100644 (file)
@@ -24,4 +24,10 @@ return [
             'Deprecation-83093-ReplaceCascadeWithTYPO3CMSExtbaseAnnotationORMCascade.rst',
         ],
     ],
+    '@validate' => [
+        'restFiles' => [
+            'Feature-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst',
+            'Deprecation-82869-ReplaceInjectWithTYPO3CMSExtbaseAnnotationInject.rst',
+        ],
+    ],
 ];