From 6ddfb9c584a0b08511642569c887e5c67b3b5b18 Mon Sep 17 00:00:00 2001
From: Alexander Schnitzler <git@alexanderschnitzler.de>
Date: Tue, 15 May 2018 15:46:29 +0200
Subject: [PATCH] [BUGFIX] Only validate method params if needed

Controller action arguments have been validated on
creation, which caused superfluous CPU cycles if the
action controller later detected, that an argument
should not have been validated at all due to an
@Extbase\IgnoreValidation annotation.

To fix this, arguments get an empty result on creation.
When setting the argument value, only the validation
results of the property mapping are merged with the
argument result.

ActionController::initializeActionMethodValidators does
only create validator instances for method arguments
that need to be validated, thus a whole bunch of checks
disappears in callActionMethod().

Releases: master
Resolves: #85012
Change-Id: Iaecf36718477a9216f8d36a993a137eb7b677227
Reviewed-on: https://review.typo3.org/56970
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
---
 ...85012-OnlyValidateMethodParamsIfNeeded.rst | 38 ++++++++++++++
 .../Mvc/Controller/ActionController.php       | 37 +++++---------
 .../Classes/Mvc/Controller/Argument.php       | 49 ++++++++++++++-----
 .../Classes/Mvc/Controller/Arguments.php      | 16 +++++-
 .../Unit/Mvc/Controller/ArgumentTest.php      |  2 +-
 .../Php/MethodCallMatcher.php                 | 14 ++++++
 6 files changed, 118 insertions(+), 38 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-85012-OnlyValidateMethodParamsIfNeeded.rst

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85012-OnlyValidateMethodParamsIfNeeded.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85012-OnlyValidateMethodParamsIfNeeded.rst
new file mode 100644
index 000000000000..6d09a511b3f8
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85012-OnlyValidateMethodParamsIfNeeded.rst
@@ -0,0 +1,38 @@
+.. include:: ../../Includes.txt
+
+===========================================================
+Deprecation: #85012 - Only validate method params if needed
+===========================================================
+
+See :issue:`85012`
+
+Description
+===========
+
+The following public methods have been marked as deprecated:
+
+* :php:`TYPO3\CMS\Extbase\Mvc\Controller\Argument::getValidationResults()`
+* :php:`TYPO3\CMS\Extbase\Mvc\Controller\Arguments::getValidationResults()`
+
+Impact
+======
+
+Calling the method triggers a deprecation log entry.
+
+
+Affected Installations
+======================
+
+Extensions that call that method. The extension scanner will find usages.
+
+
+Migration
+=========
+
+Use the following methods instead:
+
+* :php:`TYPO3\CMS\Extbase\Mvc\Controller\Argument::validate()`
+* :php:`TYPO3\CMS\Extbase\Mvc\Controller\Arguments::validate()`
+
+
+.. index:: PHP-API, FullyScanned, ext:extbase
diff --git a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php
index 4ace0e0199ca..0b7aa14c323d 100644
--- a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php
+++ b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php
@@ -257,8 +257,16 @@ class ActionController extends AbstractController
 
         $classSchema = $this->reflectionService->getClassSchema(static::class);
 
+        $ignoreValidationAnnotations = array_unique(array_flip(
+            $classSchema->getMethod($this->actionMethodName)['tags']['ignorevalidation'] ?? []
+        ));
+
         /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
         foreach ($this->arguments as $argument) {
+            if (isset($ignoreValidationAnnotations[$argument->getName()])) {
+                continue;
+            }
+
             $validator = $this->objectManager->get(ConjunctionValidator::class);
             $validatorDefinitions = $classSchema->getMethod($this->actionMethodName)['params'][$argument->getName()]['validators'] ?? [];
 
@@ -313,33 +321,12 @@ class ActionController extends AbstractController
         foreach ($this->arguments as $argument) {
             $preparedArguments[] = $argument->getValue();
         }
-        $validationResult = $this->arguments->getValidationResults();
+        $validationResult = $this->arguments->validate();
         if (!$validationResult->hasErrors()) {
             $this->emitBeforeCallActionMethodSignal($preparedArguments);
-            $actionResult = call_user_func_array([$this, $this->actionMethodName], $preparedArguments);
+            $actionResult = $this->{$this->actionMethodName}(...$preparedArguments);
         } else {
-            $methodTagsValues = $this->reflectionService->getMethodTagsValues(static::class, $this->actionMethodName);
-            $ignoreValidationAnnotations = $methodTagsValues['ignorevalidation'] ?? [];
-
-            // if there exist errors which are not ignored with @TYPO3\CMS\Extbase\Annotation\IgnoreValidation => call error method
-            // else => call action method
-            $shouldCallActionMethod = true;
-            foreach ($validationResult->getSubResults() as $argumentName => $subValidationResult) {
-                if (!$subValidationResult->hasErrors()) {
-                    continue;
-                }
-                if (in_array($argumentName, $ignoreValidationAnnotations, true)) {
-                    continue;
-                }
-                $shouldCallActionMethod = false;
-                break;
-            }
-            if ($shouldCallActionMethod) {
-                $this->emitBeforeCallActionMethodSignal($preparedArguments);
-                $actionResult = call_user_func_array([$this, $this->actionMethodName], $preparedArguments);
-            } else {
-                $actionResult = call_user_func([$this, $this->errorMethodName]);
-            }
+            $actionResult = $this->{$this->errorMethodName}();
         }
 
         if ($actionResult === null && $this->view instanceof ViewInterface) {
@@ -610,7 +597,7 @@ class ActionController extends AbstractController
         if ($referringRequest !== null) {
             $originalRequest = clone $this->request;
             $this->request->setOriginalRequest($originalRequest);
-            $this->request->setOriginalRequestMappingResults($this->arguments->getValidationResults());
+            $this->request->setOriginalRequestMappingResults($this->arguments->validate());
             $this->forward(
                 $referringRequest->getControllerActionName(),
                 $referringRequest->getControllerName(),
diff --git a/typo3/sysext/extbase/Classes/Mvc/Controller/Argument.php b/typo3/sysext/extbase/Classes/Mvc/Controller/Argument.php
index e13efb9a3717..798ae6f529a6 100644
--- a/typo3/sysext/extbase/Classes/Mvc/Controller/Argument.php
+++ b/typo3/sysext/extbase/Classes/Mvc/Controller/Argument.php
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Extbase\Mvc\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Error\Result;
 use TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException;
 use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
 
@@ -88,7 +89,12 @@ class Argument
      *
      * @var \TYPO3\CMS\Extbase\Error\Result
      */
-    protected $validationResults = null;
+    protected $validationResults;
+
+    /**
+     * @var bool
+     */
+    private $hasBeenValidated = false;
 
     /**
      * @param \TYPO3\CMS\Extbase\Property\PropertyMapper $propertyMapper
@@ -124,6 +130,8 @@ class Argument
         }
         $this->name = $name;
         $this->dataType = TypeHandlingUtility::normalizeType($dataType);
+
+        $this->validationResults = new Result();
     }
 
     /**
@@ -274,12 +282,7 @@ class Argument
                 throw $e;
             }
         }
-        $this->validationResults = $this->propertyMapper->getMessages();
-        if ($this->validator !== null) {
-            // @todo Validation API has also changed!!!
-            $validationMessages = $this->validator->validate($this->value);
-            $this->validationResults->merge($validationMessages);
-        }
+        $this->validationResults->merge($this->propertyMapper->getMessages());
         return $this;
     }
 
@@ -312,18 +315,23 @@ class Argument
      * @return bool TRUE if the argument is valid, FALSE otherwise
      * @api
      */
-    public function isValid()
+    public function isValid(): bool
     {
-        return !$this->validationResults->hasErrors();
+        return !$this->validate()->hasErrors();
     }
 
     /**
      * @return \TYPO3\CMS\Extbase\Error\Result Validation errors which have occurred.
-     * @api
+     * @deprecated
      */
     public function getValidationResults()
     {
-        return $this->validationResults;
+        trigger_error(
+            'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
+            E_USER_DEPRECATED
+        );
+
+        return $this->validate();
     }
 
     /**
@@ -336,4 +344,23 @@ class Argument
     {
         return (string)$this->value;
     }
+
+    /**
+     * @return \TYPO3\CMS\Extbase\Error\Result
+     * @api
+     */
+    public function validate(): \TYPO3\CMS\Extbase\Error\Result
+    {
+        if ($this->hasBeenValidated) {
+            return $this->validationResults;
+        }
+
+        if ($this->validator !== null) {
+            $validationMessages = $this->validator->validate($this->value);
+            $this->validationResults->merge($validationMessages);
+        }
+
+        $this->hasBeenValidated = true;
+        return $this->validationResults;
+    }
 }
diff --git a/typo3/sysext/extbase/Classes/Mvc/Controller/Arguments.php b/typo3/sysext/extbase/Classes/Mvc/Controller/Arguments.php
index c70dc455ce75..e2936fe32ce1 100644
--- a/typo3/sysext/extbase/Classes/Mvc/Controller/Arguments.php
+++ b/typo3/sysext/extbase/Classes/Mvc/Controller/Arguments.php
@@ -275,13 +275,27 @@ class Arguments extends \ArrayObject
      * Get all property mapping / validation errors
      *
      * @return \TYPO3\CMS\Extbase\Error\Result
+     * @deprecated
      */
     public function getValidationResults()
+    {
+        trigger_error(
+            'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
+            E_USER_DEPRECATED
+        );
+
+        return $this->validate();
+    }
+
+    /**
+     * @return \TYPO3\CMS\Extbase\Error\Result
+     */
+    public function validate(): \TYPO3\CMS\Extbase\Error\Result
     {
         $results = new \TYPO3\CMS\Extbase\Error\Result();
         /** @var Argument $argument */
         foreach ($this as $argument) {
-            $argumentValidationResults = $argument->getValidationResults();
+            $argumentValidationResults = $argument->validate();
             if ($argumentValidationResults === null) {
                 continue;
             }
diff --git a/typo3/sysext/extbase/Tests/Unit/Mvc/Controller/ArgumentTest.php b/typo3/sysext/extbase/Tests/Unit/Mvc/Controller/ArgumentTest.php
index 8ce709200328..165b479a03ad 100644
--- a/typo3/sysext/extbase/Tests/Unit/Mvc/Controller/ArgumentTest.php
+++ b/typo3/sysext/extbase/Tests/Unit/Mvc/Controller/ArgumentTest.php
@@ -231,7 +231,7 @@ class ArgumentTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $this->simpleValueArgument->setValidator($mockValidator);
         $this->setupPropertyMapperAndSetValue();
         $this->assertFalse($this->simpleValueArgument->isValid());
-        $this->assertEquals([$error], $this->simpleValueArgument->getValidationResults()->getErrors());
+        $this->assertEquals([$error], $this->simpleValueArgument->validate()->getErrors());
     }
 
     /**
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
index af8dbf5e417c..1dc067d4eb2e 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
@@ -2249,4 +2249,18 @@ return [
             'Deprecation-85005-DeprecateMethodsAndConstantsInValidatorResolver.rst',
         ],
     ],
+    'TYPO3\CMS\Extbase\Mvc\Controller\Argument->getValidationResults' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Deprecation-85012-OnlyValidateMethodParamsIfNeeded.rst',
+        ],
+    ],
+    'TYPO3\CMS\Extbase\Mvc\Controller\Arguments->getValidationResults' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Deprecation-85012-OnlyValidateMethodParamsIfNeeded.rst',
+        ],
+    ],
 ];
-- 
GitLab