[TASK] Avoid ObjectManager in ext:fluid RenderingContext
authorChristian Kuhn <lolli@schwarzbu.ch>
Tue, 8 Jun 2021 10:00:00 +0000 (12:00 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Wed, 9 Jun 2021 16:05:07 +0000 (18:05 +0200)
RenderingContext is a good case for a class that creates
"arbitrary" objects: The PreProcessor class names are
retrieved from global configuration in order to be
instantiated.

This initialization is moved into a (install tool mode
compatible) factory in order to transform ObjectManager
into a fallback layer for PreProcessors that are
not defined in the newer PSR container.
Therefore $container->has() is used prior to $container->get(),
in order to fallback to ObjectManager for objects that can't be
retrieved – due to missing configuration – via ContainerInterface yet.

Resolves: #94285
Related: #90803
Releases: master
Change-Id: I3fd1751b8580de7c8307e9b84da38e1551c2c622
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69412
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benjamin Franzke <bfr@qbus.de>
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benjamin Franzke <bfr@qbus.de>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/extbase/Configuration/Services.yaml
typo3/sysext/fluid/Classes/Core/Rendering/RenderingContext.php
typo3/sysext/fluid/Classes/Core/Rendering/RenderingContextFactory.php [new file with mode: 0644]
typo3/sysext/fluid/Classes/ServiceProvider.php [new file with mode: 0644]
typo3/sysext/fluid/Classes/View/AbstractTemplateView.php
typo3/sysext/fluid/Classes/View/StandaloneView.php
typo3/sysext/fluid/Configuration/Services.yaml
typo3/sysext/fluid/composer.json

index 0e4d437..3d603a1 100644 (file)
@@ -51,6 +51,10 @@ services:
     factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
     arguments: ['extbase']
 
+  TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext:
+    public: true
+    shared: false
+
   TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver: ~
   TYPO3\CMS\Extbase\Mvc\View\ViewResolverInterface: '@TYPO3\CMS\Extbase\Mvc\View\GenericViewResolver'
 
index 0752e41..ca302fd 100644 (file)
 
 namespace TYPO3\CMS\Fluid\Core\Rendering;
 
-use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 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\Fluid\Core\Cache\FluidTemplateCache;
 use TYPO3\CMS\Fluid\Core\ViewHelper\ViewHelperResolver;
 use TYPO3\CMS\Fluid\View\TemplatePaths;
+use TYPO3Fluid\Fluid\Core\Cache\FluidCacheInterface;
 use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
 use TYPO3Fluid\Fluid\Core\Parser\Configuration;
 use TYPO3Fluid\Fluid\Core\Parser\InterceptorInterface;
 use TYPO3Fluid\Fluid\Core\Parser\TemplateParser;
-use TYPO3Fluid\Fluid\Core\Parser\TemplateProcessorInterface;
 use TYPO3Fluid\Fluid\Core\Variables\StandardVariableProvider;
 use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInvoker;
 use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperVariableContainer;
@@ -41,7 +38,7 @@ class RenderingContext extends \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext
     /**
      * Controller context being passed to the ViewHelper
      *
-     * @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
+     * @var ControllerContext
      */
     protected $controllerContext;
 
@@ -61,43 +58,37 @@ class RenderingContext extends \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext
     protected $controllerAction = 'Default';
 
     /**
-     * @param \TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperVariableContainer $viewHelperVariableContainer
+     * @param ViewHelperVariableContainer $viewHelperVariableContainer
+     * @internal used by typo3/testing-framework only
      */
     public function injectViewHelperVariableContainer(ViewHelperVariableContainer $viewHelperVariableContainer)
     {
-        $this->viewHelperVariableContainer = $viewHelperVariableContainer;
+        $this->setViewHelperVariableContainer($viewHelperVariableContainer);
     }
 
-    public function __construct()
-    {
+    /**
+     * @internal constructor, use `RenderingContextFactory->create()` instead
+     */
+    public function __construct(
+        ViewHelperResolver $viewHelperResolver,
+        FluidCacheInterface $cache,
+        array $templateProcessors,
+        array $expressionNodeTypes
+    ) {
         // Reproduced partial initialisation from parent::__construct; minus the custom implementations we attach below.
         $this->setTemplateParser(new TemplateParser());
-        if (method_exists($this, 'setTemplateCompiler')) {
-            $this->setTemplateCompiler(new TemplateCompiler());
-        }
-        if (method_exists($this, 'setViewHelperInvoker')) {
-            $this->setViewHelperInvoker(new ViewHelperInvoker());
-        }
+        $this->setTemplateCompiler(new TemplateCompiler());
+        $this->setViewHelperInvoker(new ViewHelperInvoker());
         $this->setViewHelperVariableContainer(new ViewHelperVariableContainer());
         $this->setVariableProvider(new StandardVariableProvider());
 
-        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
-        if (method_exists($this, 'setTemplateProcessors')) {
-            /** @var TemplateProcessorInterface[] $processors */
-            $processors = array_map([$objectManager, 'get'], $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['preProcessors']);
-            $this->setTemplateProcessors($processors);
-        }
-        $this->setExpressionNodeTypes($GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['expressionNodeTypes']);
-        $this->setTemplatePaths($objectManager->get(TemplatePaths::class));
-        $this->setViewHelperResolver($objectManager->get(ViewHelperResolver::class));
+        $this->setTemplateProcessors($templateProcessors);
 
-        if (method_exists($this, 'setCache')) {
-            /** @var FluidTemplateCache $cache */
-            $cache = $objectManager->get(CacheManager::class)->getCache('fluid_template');
-            if (is_a($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['fluid_template']['frontend'], FluidTemplateCache::class, true)) {
-                $this->setCache($cache);
-            }
-        }
+        $this->setExpressionNodeTypes($expressionNodeTypes);
+        $this->setTemplatePaths(GeneralUtility::makeInstance(TemplatePaths::class));
+        $this->setViewHelperResolver($viewHelperResolver);
+
+        $this->setCache($cache);
     }
 
     /**
@@ -139,7 +130,7 @@ class RenderingContext extends \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext
     /**
      * Get the controller context which will be passed to the ViewHelper
      *
-     * @return \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext The controller context to set
+     * @return ControllerContext The controller context to set
      * @deprecated since v11, will be removed in v12
      */
     public function getControllerContext()
@@ -149,7 +140,7 @@ class RenderingContext extends \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext
         if ($this->controllerContext) {
             return $this->controllerContext;
         }
-        $controllerContext = GeneralUtility::makeInstance(ObjectManager::class)->get(ControllerContext::class);
+        $controllerContext = GeneralUtility::makeInstance(ControllerContext::class);
         if ($this->request) {
             $controllerContext->setRequest($this->request);
         }
@@ -202,7 +193,7 @@ class RenderingContext extends \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext
     /**
      * Set the controller context which will be passed to the ViewHelper
      *
-     * @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext The controller context to set
+     * @param ControllerContext $controllerContext The controller context to set
      */
     public function setControllerContext(ControllerContext $controllerContext)
     {
@@ -231,7 +222,7 @@ class RenderingContext extends \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext
         }
         // Also ensure that controller context is filled, if not set yet.
         if ($this->controllerContext === null) {
-            $this->controllerContext = GeneralUtility::makeInstance(ObjectManager::class)->get(ControllerContext::class);
+            $this->controllerContext = GeneralUtility::makeInstance(ControllerContext::class);
             $this->controllerContext->setRequest($request);
         }
     }
@@ -251,7 +242,7 @@ class RenderingContext extends \TYPO3Fluid\Fluid\Core\Rendering\RenderingContext
      */
     public function getUriBuilder(): UriBuilder
     {
-        $uriBuilder = GeneralUtility::makeInstance(ObjectManager::class)->get(UriBuilder::class);
+        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
         $uriBuilder->setRequest($this->request);
         return $uriBuilder;
     }
diff --git a/typo3/sysext/fluid/Classes/Core/Rendering/RenderingContextFactory.php b/typo3/sysext/fluid/Classes/Core/Rendering/RenderingContextFactory.php
new file mode 100644 (file)
index 0000000..1e754fe
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * 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!
+ */
+
+namespace TYPO3\CMS\Fluid\Core\Rendering;
+
+use Psr\Container\ContainerInterface;
+use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\DependencyInjection\FailsafeContainer;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+use TYPO3\CMS\Fluid\Core\ViewHelper\ViewHelperResolver;
+use TYPO3Fluid\Fluid\Core\Cache\FluidCacheInterface;
+use TYPO3Fluid\Fluid\Core\Parser\TemplateProcessor\EscapingModifierTemplateProcessor;
+use TYPO3Fluid\Fluid\Core\Parser\TemplateProcessor\NamespaceDetectionTemplateProcessor;
+use TYPO3Fluid\Fluid\Core\Parser\TemplateProcessor\PassthroughSourceModifierTemplateProcessor;
+use TYPO3Fluid\Fluid\Core\Parser\TemplateProcessorInterface;
+
+/**
+ * Factory class registered in ServiceProvider to create a RenderingContext.
+ *
+ * This is a low level factory always registered, even in failsafe mode: fluid
+ * is needed in install tool which does not rely on the normal (cached) symfony DI
+ * mechanism - Services.yaml is ignored in failsafe mode.
+ *
+ * A casual failsafe instantiation / injection using ServiceProvider.php wouldn't
+ * need this factory - the ViewHelperResolver is a "normal" case. But the failsafe
+ * mechanism is strict and relies on two things: The service is public: true,
+ * this is the case with RenderingContext. And the service is shared: true - a
+ * stateless singleton. This is not true for RenderingContext, it by definition
+ * relies on context and must be created a-new per fluid parsing instance.
+ *
+ * To allow creating RenderingContext objects in failsafe mode, this factory
+ * is registered as service provider to dynamically prepare instances.
+ */
+class RenderingContextFactory
+{
+    private ContainerInterface $container;
+    private CacheManager $cacheManager;
+    private ViewHelperResolver $viewHelperResolver;
+
+    public function __construct(
+        ContainerInterface $container,
+        CacheManager $cacheManager,
+        ViewHelperResolver $viewHelperResolver
+    ) {
+        $this->container = $container;
+        $this->cacheManager = $cacheManager;
+        $this->viewHelperResolver = $viewHelperResolver;
+    }
+
+    public function create(): RenderingContext
+    {
+        /** @var TemplateProcessorInterface[] $processors */
+        $processors = [];
+
+        if ($this->container instanceof FailsafeContainer) {
+            // Load default set of processors in failsafe mode (install tool context)
+            // as custom processors can not be retrieved from the symfony container
+            $processors = [
+                new EscapingModifierTemplateProcessor(),
+                new PassthroughSourceModifierTemplateProcessor(),
+                new NamespaceDetectionTemplateProcessor()
+            ];
+        } else {
+            foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['preProcessors'] as $className) {
+                if ($this->container->has($className)) {
+                    /** @var TemplateProcessorInterface[] $processors */
+                    $processors[] = $this->container->get($className);
+                } else {
+                    // @deprecated: Layer for processors that can't be instantiated by symfony-DI yet,
+                    // probably due to a missing Services.yaml in the providing extension. Fall back to ObjectManager,
+                    // which logs a deprecation. If condition and else can be dropped in v12.
+                    $objectManager = $this->container->get(ObjectManager::class);
+                    /** @var TemplateProcessorInterface[] $processors */
+                    $processors[] = $objectManager->get($className);
+                }
+            }
+        }
+
+        $cache = $this->cacheManager->getCache('fluid_template');
+        if (!$cache instanceof FluidCacheInterface) {
+            throw new \RuntimeException('Cache fluid_template must implement FluidCacheInterface', 1623148753);
+        }
+
+        return new RenderingContext(
+            $this->viewHelperResolver,
+            $cache,
+            $processors,
+            $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['expressionNodeTypes']
+        );
+    }
+}
diff --git a/typo3/sysext/fluid/Classes/ServiceProvider.php b/typo3/sysext/fluid/Classes/ServiceProvider.php
new file mode 100644 (file)
index 0000000..c6197d5
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * 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!
+ */
+
+namespace TYPO3\CMS\Fluid;
+
+use Psr\Container\ContainerInterface;
+use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Package\AbstractServiceProvider;
+
+/**
+ * @internal
+ */
+class ServiceProvider extends AbstractServiceProvider
+{
+    protected static function getPackagePath(): string
+    {
+        return __DIR__ . '/../';
+    }
+
+    public function getFactories(): array
+    {
+        return [
+            Core\Rendering\RenderingContextFactory::class => [ static::class, 'getRenderingContextFactory' ],
+            Core\ViewHelper\ViewHelperResolver::class => [ static::class, 'getViewHelperResolver' ],
+        ];
+    }
+
+    public static function getRenderingContextFactory(ContainerInterface $container): Core\Rendering\RenderingContextFactory
+    {
+        return self::new($container, Core\Rendering\RenderingContextFactory::class, [
+            $container,
+            $container->get(CacheManager::class),
+            $container->get(Core\ViewHelper\ViewHelperResolver::class)
+        ]);
+    }
+
+    public static function getViewHelperResolver(ContainerInterface $container): Core\ViewHelper\ViewHelperResolver
+    {
+        return self::new($container, Core\ViewHelper\ViewHelperResolver::class);
+    }
+}
index 21392b5..bee7b7d 100644 (file)
@@ -18,8 +18,8 @@ namespace TYPO3\CMS\Fluid\View;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext;
 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
-use TYPO3\CMS\Extbase\Object\ObjectManager;
 use TYPO3\CMS\Fluid\Core\Rendering\RenderingContext;
+use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory;
 use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
 use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException;
 use TYPO3Fluid\Fluid\View\TemplateView;
@@ -50,7 +50,7 @@ abstract class AbstractTemplateView extends TemplateView implements ViewInterfac
     public function __construct(RenderingContextInterface $context = null)
     {
         if (!$context) {
-            $context = GeneralUtility::makeInstance(ObjectManager::class)->get(RenderingContext::class);
+            $context = GeneralUtility::makeInstance(RenderingContextFactory::class)->create();
         }
         parent::__construct($context);
     }
index 5052c85..205fc97 100644 (file)
@@ -18,8 +18,8 @@ namespace TYPO3\CMS\Fluid\View;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
 use TYPO3\CMS\Extbase\Mvc\Request;
-use TYPO3\CMS\Extbase\Object\ObjectManager;
 use TYPO3\CMS\Fluid\Core\Rendering\RenderingContext;
+use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException;
 
@@ -38,8 +38,6 @@ class StandaloneView extends AbstractTemplateView
      */
     public function __construct(ContentObjectRenderer $contentObject = null)
     {
-        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
-
         // @todo: this needs to be removed in the future
         $configurationManager = GeneralUtility::getContainer()->get(ConfigurationManager::class);
         if ($contentObject === null) {
@@ -48,8 +46,8 @@ class StandaloneView extends AbstractTemplateView
         }
         $configurationManager->setContentObject($contentObject);
 
-        $request = $objectManager->get(Request::class);
-        $renderingContext = $objectManager->get(RenderingContext::class);
+        $request = GeneralUtility::makeInstance(Request::class);
+        $renderingContext = GeneralUtility::makeInstance(RenderingContextFactory::class)->create();
         $renderingContext->setRequest($request);
         parent::__construct($renderingContext);
     }
index c822b70..5097967 100644 (file)
@@ -17,3 +17,25 @@ services:
     class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
     factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
     arguments: ['fluid_template']
+
+  # @deprecated since v11, will be removed in v12 - RenderingContext must be injected / instantiated using factory.
+  TYPO3\CMS\Fluid\Core\Rendering\RenderingContext:
+    factory: ['@TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory', 'create']
+    autowire: false
+    shared: false
+    public: true
+    deprecated:
+      package: 'typo3/cms-fluid'
+      version: '11.3'
+      message: 'Injection/Instantiation of "%service_id%" is deprecated. Please use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory->create().'
+
+  # Classes of base package
+  TYPO3Fluid\Fluid\Core\Parser\TemplateProcessor\EscapingModifierTemplateProcessor:
+    public: true
+    shared: false
+  TYPO3Fluid\Fluid\Core\Parser\TemplateProcessor\PassthroughSourceModifierTemplateProcessor:
+    public: true
+    shared: false
+  TYPO3Fluid\Fluid\Core\Parser\TemplateProcessor\NamespaceDetectionTemplateProcessor:
+    public: true
+    shared: false
index 71786a0..5a465df 100644 (file)
@@ -33,6 +33,7 @@
                },
                "typo3/cms": {
                        "Package": {
+                               "serviceProvider": "TYPO3\\CMS\\Fluid\\ServiceProvider",
                                "protected": true,
                                "partOfFactoryDefault": true,
                                "partOfMinimalUsableSystem": true