[TASK] Avoid ObjectManager in ext:form FormRuntime
authorChristian Kuhn <lolli@schwarzbu.ch>
Thu, 17 Jun 2021 21:13:02 +0000 (23:13 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Fri, 18 Jun 2021 17:14:47 +0000 (19:14 +0200)
Most ObjectManager->get() calls in FormRuntime can
be moved to makeInstance() directly without further
impact. For FormFactoryInterface, a b/w compat layer
is established, but shouldn't be problematic since
this will be ArrayFormFactory in almost all cases.

Change-Id: I354d582260307986cb386506fa5e006646ca816a
Resolves: #94370
Related: #90803
Releases: master
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69515
Tested-by: core-ci <typo3@b13.com>
Tested-by: Jochen <rothjochen@gmail.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Jochen <rothjochen@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/form/Classes/Controller/FormEditorController.php
typo3/sysext/form/Classes/Domain/Finishers/FinisherContext.php
typo3/sysext/form/Classes/Domain/Model/FormDefinition.php
typo3/sysext/form/Classes/Domain/Renderer/FluidFormRenderer.php
typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php
typo3/sysext/form/Classes/ViewHelpers/RenderViewHelper.php
typo3/sysext/form/Configuration/Services.yaml
typo3/sysext/form/Documentation/I/ApiReference/Index.rst
typo3/sysext/form/Documentation/I/Concepts/FrontendRendering/Index.rst
typo3/sysext/form/Tests/Unit/Domain/Runtime/FormRuntimeTest.php

index e8673cb..ab1c9b3 100644 (file)
@@ -21,7 +21,6 @@ use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
 use TYPO3\CMS\Backend\View\BackendTemplateView;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
@@ -225,8 +224,7 @@ class FormEditorController extends AbstractBackendController
         $formFactory = GeneralUtility::makeInstance(ArrayFormFactory::class);
         $formDefinition = $formFactory->build($formDefinition, $prototypeName);
         $formDefinition->setRenderingOption('previewMode', true);
-        $response = new Response();
-        $form = $formDefinition->bind($this->request, $response);
+        $form = $formDefinition->bind($this->request);
         $form->setCurrentSiteLanguage($this->buildFakeSiteLanguage(0, 0));
         $form->overrideCurrentPage($pageIndex);
 
index 49f6a6a..66866b0 100644 (file)
@@ -45,25 +45,19 @@ class FinisherContext
     protected $cancelled = false;
 
     /**
-     * A reference to the Form Runtime that the finisher belongs to
-     *
-     * @var \TYPO3\CMS\Form\Domain\Runtime\FormRuntime
+     * A reference to the Form Runtime the finisher belongs to
      */
-    protected $formRuntime;
+    protected FormRuntime $formRuntime;
 
     /**
      * The assigned controller context which might be needed by the finisher.
-     *
-     * @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
      */
-    protected $controllerContext;
+    protected ControllerContext $controllerContext;
 
     /**
      * The assigned controller context which might be needed by the finisher.
-     *
-     * @var FinisherVariableProvider
      */
-    protected $finisherVariableProvider;
+    protected FinisherVariableProvider $finisherVariableProvider;
 
     private Request $request;
 
@@ -78,13 +72,6 @@ class FinisherContext
         $this->formRuntime = $formRuntime;
         $this->controllerContext = $controllerContext;
         $this->request = $request;
-    }
-
-    /**
-     * Sets up the FinisherVariableProvider
-     */
-    public function initializeObject()
-    {
         $this->finisherVariableProvider = GeneralUtility::makeInstance(FinisherVariableProvider::class);
     }
 
index 61ce569..fb09563 100644 (file)
@@ -21,7 +21,6 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Form\Domain\Model;
 
-use Psr\Http\Message\ResponseInterface;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Mvc\Request;
@@ -208,9 +207,8 @@ use TYPO3\CMS\Form\Mvc\ProcessingRule;
  *
  * /---code php
  * # $currentRequest and $currentResponse need to be available, f.e. inside a controller you would
- * # use $this->request and $this->response; inside a ViewHelper you would use $this->controllerContext->getRequest()
- * # and $this->controllerContext->getResponse()
- * $form = $formDefinition->bind($currentRequest, $currentResponse);
+ * # use $this->request. Inside a ViewHelper you would use $this->controllerContext->getRequest()
+ * $form = $formDefinition->bind($currentRequest);
  *
  * # now, you can use the $form object to get information about the currently
  * # entered values into the form, etc.
@@ -220,6 +218,9 @@ use TYPO3\CMS\Form\Mvc\ProcessingRule;
  *
  * Scope: frontend
  * **This class is NOT meant to be sub classed by developers.**
+ *
+ * @internal May change any time, use FormFactoryInterface to select a different FormDefinition if needed
+ * @todo: Declare final in v12
  */
 class FormDefinition extends AbstractCompositeRenderable implements VariableRenderableInterface
 {
@@ -675,12 +676,15 @@ class FormDefinition extends AbstractCompositeRenderable implements VariableRend
      * a new "instance" of the Form.
      *
      * @param Request $request
-     * @param ResponseInterface $response
      * @return FormRuntime
      */
-    public function bind(Request $request, ResponseInterface $response): FormRuntime
+    public function bind(Request $request): FormRuntime
     {
-        return GeneralUtility::makeInstance(FormRuntime::class, $this, $request, $response);
+        $formRuntime = GeneralUtility::makeInstance(FormRuntime::class);
+        $formRuntime->setFormDefinition($this);
+        $formRuntime->setRequest($request);
+        $formRuntime->initialize();
+        return $formRuntime;
     }
 
     /**
index 45c7a9a..9d35cdf 100644 (file)
@@ -27,7 +27,7 @@ use TYPO3\CMS\Form\Domain\Exception\RenderingException;
 use TYPO3\CMS\Form\ViewHelpers\RenderRenderableViewHelper;
 
 /**
- * A fluid RendererInterface implementation which used to render a *FormDefinition*.
+ * A fluid RendererInterface implementation used to render a *FormDefinition*.
  *
  * This renderer is called from {@link \TYPO3\CMS\Form\Domain\Runtime\FormRuntime::render()}.
  *
index 6aecdfc..8deb4fb 100644 (file)
@@ -21,12 +21,14 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Form\Domain\Runtime;
 
+use Psr\Container\ContainerInterface;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Error\Http\BadRequestException;
 use TYPO3\CMS\Core\ExpressionLanguage\Resolver;
 use TYPO3\CMS\Core\Http\ApplicationType;
+use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
@@ -39,7 +41,6 @@ use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
 use TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext;
 use TYPO3\CMS\Extbase\Mvc\Request;
 use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
-use TYPO3\CMS\Extbase\Object\ObjectManager;
 use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
 use TYPO3\CMS\Extbase\Property\Exception as PropertyException;
 use TYPO3\CMS\Extbase\Security\Cryptography\HashService;
@@ -76,7 +77,7 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
  * That's easy, just call render() on the FormRuntime:
  *
  * /---code php
- * $form = $formDefinition->bind($request, $response);
+ * $form = $formDefinition->bind($request);
  * $renderedForm = $form->render();
  * \---
  *
@@ -100,20 +101,24 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
  *
  * Scope: frontend
  * **This class is NOT meant to be sub classed by developers.**
+ *
+ * @internal High cohesion to FormDefinition, may change any time
+ * @todo: Declare final in v12
  */
 class FormRuntime implements RootRenderableInterface, \ArrayAccess
 {
     const HONEYPOT_NAME_SESSION_IDENTIFIER = 'tx_form_honeypot_name_';
 
+    protected ContainerInterface $container;
     protected ObjectManagerInterface $objectManager;
-    protected FormDefinition $formDefinition;
-    protected Request $request;
+    protected ?FormDefinition $formDefinition = null;
+    protected ?Request $request = null;
     protected ResponseInterface $response;
     protected HashService $hashService;
     protected ConfigurationManagerInterface $configurationManager;
 
     /**
-     * @var \TYPO3\CMS\Form\Domain\Runtime\FormState
+     * @var FormState
      */
     protected $formState;
 
@@ -133,7 +138,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
      * finishing actions need to take place. You should use $this->isAfterLastPage()
      * instead of explicitly checking for NULL.
      *
-     * @var \TYPO3\CMS\Form\Domain\Model\FormElements\Page|null
+     * @var Page|null
      */
     protected $currentPage;
 
@@ -141,7 +146,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
      * Reference to the page which has been shown on the last request (i.e.
      * we have to handle the submitted data from lastDisplayedPage)
      *
-     * @var \TYPO3\CMS\Form\Domain\Model\FormElements\Page
+     * @var Page
      */
     protected $lastDisplayedPage;
 
@@ -155,29 +160,41 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
     /**
      * Reference to the current running finisher
      *
-     * @var \TYPO3\CMS\Form\Domain\Finishers\FinisherInterface
+     * @var FinisherInterface
      */
     protected $currentFinisher;
 
-    /**
-     * @param FormDefinition $formDefinition
-     * @param Request $request
-     * @param ResponseInterface $response
-     */
-    public function __construct(FormDefinition $formDefinition, Request $request, ResponseInterface $response)
+    public function __construct(
+        ContainerInterface $container,
+        ObjectManagerInterface $objectManager,
+        ConfigurationManagerInterface $configurationManager,
+        HashService $hashService
+    ) {
+        $this->container = $container;
+        // @deprecated since v11, will be removed in v12
+        $this->objectManager = $objectManager;
+        $this->configurationManager = $configurationManager;
+        $this->hashService = $hashService;
+        $this->response = new Response();
+    }
+
+    public function setFormDefinition(FormDefinition $formDefinition)
     {
         $this->formDefinition = $formDefinition;
-        $arguments = $request->getArguments();
+    }
+
+    public function setRequest(Request $request)
+    {
         $this->request = clone $request;
+    }
+
+    public function initialize()
+    {
+        $arguments = $this->request->getArguments();
         $formIdentifier = $this->formDefinition->getIdentifier();
         if (isset($arguments[$formIdentifier])) {
             $this->request->setArguments($arguments[$formIdentifier]);
         }
-        $this->response = $response;
-
-        $this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
-        $this->configurationManager = GeneralUtility::makeInstance(ConfigurationManagerInterface::class);
-        $this->hashService = GeneralUtility::makeInstance(HashService::class);
 
         $this->initializeCurrentSiteLanguage();
         $this->initializeFormSessionFromRequest();
@@ -343,7 +360,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
             $honeypotNameFromSession = $this->getHoneypotNameFromSession($this->lastDisplayedPage);
             if ($honeypotNameFromSession) {
                 $honeypotElement = $this->lastDisplayedPage->createElement($honeypotNameFromSession, $renderingOptions['honeypot']['formElementToUse']);
-                $validator = $this->objectManager->get(EmptyValidator::class);
+                $validator = GeneralUtility::makeInstance(EmptyValidator::class);
                 $honeypotElement->addValidator($validator);
             }
         }
@@ -388,7 +405,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
 
             $referenceElement = $this->currentPage->getElements()[$randomElementNumber];
             $honeypotElement = $this->currentPage->createElement($honeypotName, $renderingOptions['honeypot']['formElementToUse']);
-            $validator = $this->objectManager->get(EmptyValidator::class);
+            $validator = GeneralUtility::makeInstance(EmptyValidator::class);
 
             $honeypotElement->addValidator($validator);
             if (random_int(0, 1) === 1) {
@@ -544,7 +561,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
      */
     protected function mapAndValidatePage(Page $page): Result
     {
-        $result = $this->objectManager->get(Result::class);
+        $result = GeneralUtility::makeInstance(Result::class);
         $requestArguments = $this->request->getArguments();
 
         $propertyPathsForWhichPropertyMappingShouldHappen = [];
@@ -659,7 +676,12 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
             throw new RenderingException(sprintf('The form definition "%s" does not have a rendererClassName set.', $this->formDefinition->getIdentifier()), 1326095912);
         }
         $rendererClassName = $this->formDefinition->getRendererClassName();
-        $renderer = $this->objectManager->get($rendererClassName);
+        if ($this->container->has($rendererClassName)) {
+            $renderer = $this->container->get($rendererClassName);
+        } else {
+            // @deprecated since v11, will be removed in v12.
+            $renderer = $this->objectManager->get($rendererClassName);
+        }
         if (!($renderer instanceof RendererInterface)) {
             throw new RenderingException(sprintf('The renderer "%s" des not implement RendererInterface', $rendererClassName), 1326096024);
         }
@@ -678,7 +700,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
      */
     protected function invokeFinishers(): string
     {
-        $finisherContext = $this->objectManager->get(
+        $finisherContext = GeneralUtility::makeInstance(
             FinisherContext::class,
             $this,
             $this->getControllerContext(),
@@ -874,11 +896,11 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
      */
     protected function getControllerContext(): ControllerContext
     {
-        $uriBuilder = $this->objectManager->get(UriBuilder::class);
+        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
         $uriBuilder->setRequest($this->request);
-        $controllerContext = $this->objectManager->get(ControllerContext::class);
+        $controllerContext = GeneralUtility::makeInstance(ControllerContext::class);
         $controllerContext->setRequest($this->request);
-        $controllerContext->setArguments($this->objectManager->get(Arguments::class, []));
+        $controllerContext->setArguments(GeneralUtility::makeInstance(Arguments::class));
         $controllerContext->setUriBuilder($uriBuilder);
         return $controllerContext;
     }
index 92dfded..4b2955d 100644 (file)
@@ -21,7 +21,6 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Form\ViewHelpers;
 
-use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
@@ -98,12 +97,19 @@ class RenderViewHelper extends AbstractViewHelper
             $prototypeName = $overrideConfiguration['prototypeName'] ?? 'standard';
         }
 
-        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
-        /** @var FormFactoryInterface $factory */
-        $factory = $objectManager->get($factoryClass);
+        // Even though getContainer() is internal, we can't get container injected here due to static scope
+        $container = GeneralUtility::getContainer();
+        if ($container->has($factoryClass)) {
+            /** @var FormFactoryInterface $factory */
+            $factory = $container->get($factoryClass);
+        } else {
+            // @deprecated since TYPO3 v11, will be removed in TYPO3 v12.0
+            /** @var FormFactoryInterface $factory */
+            $factory = GeneralUtility::makeInstance(ObjectManager::class)->get($factoryClass);
+        }
+
         $formDefinition = $factory->build($overrideConfiguration, $prototypeName);
-        $response = new Response();
-        $form = $formDefinition->bind($renderingContext->getRequest(), $response);
+        $form = $formDefinition->bind($renderingContext->getRequest());
 
         // If the controller context does not contain a response object, this viewhelper is used in a
         // fluid template rendered by the FluidTemplateContentObject. Handle the StopActionException
index b5236c8..1c14dd7 100644 (file)
@@ -19,6 +19,18 @@ services:
     alias: TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager
     public: true
 
+  TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory:
+    public: true
+    shared: false
+
+  TYPO3\CMS\Form\Domain\Renderer\FluidFormRenderer:
+    public: true
+    shared: false
+
+  TYPO3\CMS\Form\Domain\Runtime\FormRuntime:
+    public: true
+    shared: false
+
   TYPO3\CMS\Form\Mvc\ProcessingRule:
     public: true
     shared: false
index 7e8e2ad..a4aaf5f 100644 (file)
@@ -453,7 +453,7 @@ Signature::
 
 Example::
 
-   $form = $formDefinition->bind($this->request, $this->response);
+   $form = $formDefinition->bind($this->request);
    $form->overrideCurrentPage($pageIndex);
 
 
@@ -740,7 +740,7 @@ Bind the current request and response to this form instance, effectively creatin
 
 Signature::
 
-   public function bind(Request $request, Response $response): FormRuntime;
+   public function bind(Request $request): FormRuntime;
 
 
 .. _apireference-frontendrendering-programmatically-apimethods-formdefinition-getprocessingrule:
index ca4248d..b6e874b 100644 (file)
@@ -157,9 +157,9 @@ To trigger the rendering of a ``FormDefinition`` domain model, the current
 object which contains the ``Runtime State`` of the form. Among other things,
 this object includes the currently inserted values::
 
-   // $currentRequest and $currentResponse need to be available
-   // inside a controller, you would use $this->request and $this->response;
-   $form = $formDefinition->bind($currentRequest, $currentResponse);
+   // $currentRequest needs to be available.
+   // Inside a controller, you would use $this->request
+   $form = $formDefinition->bind($currentRequest);
    // now, you can use the $form object to get information about the currently entered values, etc.
 
 
@@ -184,7 +184,7 @@ Rendering a form
 
 Rendering a form is easy. Just call ``render()`` on the ``FormRuntime``::
 
-   $form = $formDefinition->bind($request, $response);
+   $form = $formDefinition->bind($request);
    $renderedForm = $form->render();
 
 
index 861c0ca..cc424c8 100644 (file)
@@ -15,8 +15,7 @@
 
 namespace TYPO3\CMS\Form\Tests\Unit\Domain\Runtime;
 
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Extbase\Object\ObjectManager;
+use Psr\Container\ContainerInterface;
 use TYPO3\CMS\Form\Domain\Exception\RenderingException;
 use TYPO3\CMS\Form\Domain\Model\FormDefinition;
 use TYPO3\CMS\Form\Domain\Model\FormElements\Page;
@@ -89,9 +88,6 @@ class FormRuntimeTest extends UnitTestCase
      */
     public function renderThrowsExceptionIfRendererClassNameInstanceDoesNotImplementRendererInterface(): void
     {
-        $objectManagerProphecy = $this->prophesize(ObjectManager::class);
-        GeneralUtility::setSingletonInstance(ObjectManager::class, $objectManagerProphecy->reveal());
-
         $mockFormRuntime = $this->getAccessibleMock(FormRuntime::class, [
             'isAfterLastPage', 'processVariants'
         ], [], '', false);
@@ -107,34 +103,22 @@ class FormRuntimeTest extends UnitTestCase
             'getIdentifier'
         ])->disableOriginalConstructor()->getMock();
 
-        $mockPage
-            ->method('getIndex')
-            ->willReturn(1);
+        $mockPage->method('getIndex')->willReturn(1);
 
-        $mockFormDefinition
-            ->method('getRendererClassName')
-            ->willReturn('fooRenderer');
+        $mockFormDefinition->method('getRendererClassName')->willReturn('fooRenderer');
+        $mockFormDefinition->method('getIdentifier')->willReturn('text-1');
 
-        $mockFormDefinition
-            ->method('getIdentifier')
-            ->willReturn('text-1');
-
-        $mockFormRuntime
-            ->method('isAfterLastPage')
-            ->willReturn(false);
-
-        $mockFormRuntime
-            ->method('processVariants')
-            ->willReturn(null);
+        $mockFormRuntime->method('isAfterLastPage')->willReturn(false);
+        $mockFormRuntime->method('processVariants')->willReturn(null);
 
-        $objectManagerProphecy
-            ->get('fooRenderer')
-            ->willReturn(new \stdClass());
+        $containerProphecy = $this->prophesize(ContainerInterface::class);
+        $containerProphecy->has('fooRenderer')->willReturn(true)->shouldBeCalled();
+        $containerProphecy->get('fooRenderer')->willReturn(new \stdClass())->shouldBeCalled();
 
         $mockFormRuntime->_set('formState', $mockFormState);
         $mockFormRuntime->_set('currentPage', $mockPage);
         $mockFormRuntime->_set('formDefinition', $mockFormDefinition);
-        $mockFormRuntime->_set('objectManager', $objectManagerProphecy->reveal());
+        $mockFormRuntime->_set('container', $containerProphecy->reveal());
 
         $this->expectException(RenderingException::class);
         $this->expectExceptionCode(1326096024);