[!!!][TASK] Configure extbase request handlers via PHP 26/61226/6
authorAlexander Schnitzler <git@alexanderschnitzler.de>
Thu, 4 Jul 2019 15:04:28 +0000 (17:04 +0200)
committerAndreas Fernandez <a.fernandez@scripting-base.de>
Fri, 5 Jul 2019 15:30:26 +0000 (17:30 +0200)
This patch removes the ability to configure extbase
request handlers via typoscript. This is done because
typoscript is too variable, i.e. the configuration may
change depending on the day, the hour and whatever
possibility typoscript offers when it comes to conditions.

The ability to configure request handlers does not vanish
but needs to be immutable and predictable at an early
stage of the runtime.

To achieve this, the configuration has to be put into the
file "EXT:Configuration/Extbase/RequestHandlers.php".

This patch is considered breaking as the configuration via
typoscript stops working immediately and the configuration
syntax slightly changed.

The easiest way to migrate to the new syntax is to have a
look at configuration files in core extensions.

With typoscript, it was also possible to override
configuration keys and therefore completely override
request handlers set by core extensions. This is no
longer possible and considered bad practice anyway.
A suitable request handler is now only determined by
its priority.

Releases: master
Resolves: #88687
Change-Id: I915d2af221f76c1ee882761f2087b985e5584bb0
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61226
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
12 files changed:
typo3/sysext/core/Documentation/Changelog/master/Breaking-88687-ConfigureExtbaseRequestHandlersViaPHP.rst [new file with mode: 0644]
typo3/sysext/extbase/Classes/Configuration/BackendConfigurationManager.php
typo3/sysext/extbase/Classes/Configuration/RequestHandlersConfiguration.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Configuration/RequestHandlersConfigurationFactory.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Core/Bootstrap.php
typo3/sysext/extbase/Classes/Mvc/RequestHandlerResolver.php
typo3/sysext/extbase/Configuration/Extbase/RequestHandlers.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Configuration/RequestHandlerConfigurationFactoryTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Unit/Configuration/BackendConfigurationManagerTest.php
typo3/sysext/extbase/ext_typoscript_setup.typoscript
typo3/sysext/fluid/Configuration/Extbase/RequestHandlers.php [new file with mode: 0644]
typo3/sysext/fluid/ext_typoscript_setup.typoscript

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-88687-ConfigureExtbaseRequestHandlersViaPHP.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-88687-ConfigureExtbaseRequestHandlersViaPHP.rst
new file mode 100644 (file)
index 0000000..0b4e8a0
--- /dev/null
@@ -0,0 +1,67 @@
+.. include:: ../../Includes.txt
+
+=============================================================
+Breaking: #88687 - Configure extbase request handlers via PHP
+=============================================================
+
+See :issue:`88687`
+
+Description
+===========
+
+The configuration of extbase request handlers is no longer possible via typoscript.
+All typoscript concerning the configuration of request handlers needs to be converted to php, residing
+in `EXT:Configuration/Extbase/RequestHandlers.php`.
+
+
+Impact
+======
+
+Unless converted to php, the configuration in typoscript does no longer have any effect and therefore the registration
+of request handlers will no longer work.
+
+
+Affected Installations
+======================
+
+All installations that configure request handlers via typoscript.
+
+
+Migration
+=========
+
+Every extension that used typoscript for such configuration must provide a php configuration class called:
+`EXT:Configuration/Extbase/RequestHandlers.php`
+
+The migration is best described by an example:
+
+.. code-block:: typoscript
+
+   config.tx_extbase {
+       mvc {
+           requestHandlers {
+               Vendor\Extension\Mvc\Web\FrontendRequestHandler = Vendor\Extension\Mvc\Web\FrontendRequestHandler
+           }
+       }
+   }
+
+This configuration will look like this, defined in php:
+
+.. code-block:: php
+
+   <?php
+   declare(strict_types = 1);
+
+   return [
+       \Vendor\Extension\Mvc\Web\FrontendRequestHandler::class,
+   ];
+
+.. warning::
+
+   With typoscript it was possible to override request handlers, registered by extensions loaded before the current one.
+   This also included core extensions. This approach has been bad practice because suitable request handlers are chosen
+   by their ability to handle a request and their priority. The evaluation of priorities could have been bypassed by
+   overriding keys of the configuration. This is no longer possible as request handler configuration files can only
+   add possible request handlers. Hence the omitted keys in the configuration array.
+
+.. index:: TypoScript, NotScanned, ext:extbase
index 4c78d95..f558254 100644 (file)
@@ -25,8 +25,6 @@ use TYPO3\CMS\Core\TypoScript\TemplateService;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\RootlineUtility;
-use TYPO3\CMS\Extbase\Mvc\Web\BackendRequestHandler;
-use TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler;
 
 /**
  * A general purpose configuration manager used in backend mode.
@@ -239,12 +237,6 @@ class BackendConfigurationManager extends AbstractConfigurationManager
      */
     protected function getContextSpecificFrameworkConfiguration(array $frameworkConfiguration): array
     {
-        if (!isset($frameworkConfiguration['mvc']['requestHandlers'])) {
-            $frameworkConfiguration['mvc']['requestHandlers'] = [
-                FrontendRequestHandler::class => FrontendRequestHandler::class,
-                BackendRequestHandler::class => BackendRequestHandler::class
-            ];
-        }
         return $frameworkConfiguration;
     }
 
diff --git a/typo3/sysext/extbase/Classes/Configuration/RequestHandlersConfiguration.php b/typo3/sysext/extbase/Classes/Configuration/RequestHandlersConfiguration.php
new file mode 100644 (file)
index 0000000..99a04b9
--- /dev/null
@@ -0,0 +1,43 @@
+<?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\Extbase\Configuration;
+
+/**
+ * @internal only to be used within Extbase, not part of TYPO3 Core API.
+ */
+class RequestHandlersConfiguration
+{
+    /**
+     * @var array
+     */
+    private $configuration;
+
+    /**
+     * @param array $configuration
+     */
+    public function __construct(array $configuration)
+    {
+        $this->configuration = $configuration;
+    }
+
+    /**
+     * @return array|string[]
+     */
+    public function getRegisteredRequestHandlers(): array
+    {
+        return $this->configuration;
+    }
+}
diff --git a/typo3/sysext/extbase/Classes/Configuration/RequestHandlersConfigurationFactory.php b/typo3/sysext/extbase/Classes/Configuration/RequestHandlersConfigurationFactory.php
new file mode 100644 (file)
index 0000000..a4f8156
--- /dev/null
@@ -0,0 +1,112 @@
+<?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\Extbase\Configuration;
+
+use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\CMS\Core\Cache\Frontend\NullFrontend;
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Mvc\RequestHandlerInterface;
+
+/**
+ * @internal only to be used within Extbase, not part of TYPO3 Core API.
+ */
+final class RequestHandlersConfigurationFactory implements SingletonInterface
+{
+    /**
+     * @var FrontendInterface
+     */
+    private $cacheFrontend;
+
+    /**
+     * @param CacheManager|null $cacheManager can be null to disable caching in this factory
+     */
+    public function __construct(CacheManager $cacheManager = null)
+    {
+        $cacheIdentifier = 'extbase';
+
+        $cacheFrontend = new NullFrontend($cacheIdentifier);
+        if ($cacheManager !== null) {
+            try {
+                $cacheFrontend = $cacheManager->getCache($cacheIdentifier);
+            } catch (NoSuchCacheException $e) {
+                // Handling this exception is not needed as $cacheFrontend is
+                // a NullFrontend at this moment.
+            }
+        }
+
+        $this->cacheFrontend = $cacheFrontend;
+    }
+
+    /**
+     * @return RequestHandlersConfiguration
+     * @throws Exception
+     */
+    public function createRequestHandlersConfiguration(): RequestHandlersConfiguration
+    {
+        $cacheEntryIdentifier = 'RequestHandlers_' . sha1(TYPO3_version . Environment::getProjectPath());
+
+        if ($this->cacheFrontend->has($cacheEntryIdentifier)) {
+            return new RequestHandlersConfiguration($this->cacheFrontend->get($cacheEntryIdentifier));
+        }
+
+        $classes = [];
+        foreach (GeneralUtility::makeInstance(PackageManager::class)->getActivePackages() as $activePackage) {
+            $requestHandlersFile = $activePackage->getPackagePath() . 'Configuration/Extbase/RequestHandlers.php';
+            if (file_exists($requestHandlersFile)) {
+                $definedClasses = require $requestHandlersFile;
+                if (!is_array($definedClasses)) {
+                    continue;
+                }
+
+                foreach ($definedClasses as $definedClass) {
+                    if (!class_exists($definedClass)) {
+                        throw new Exception(
+                            sprintf(
+                                'Request class "%s", registered in "%s", does not exist.',
+                                $definedClass,
+                                $requestHandlersFile
+                            ),
+                            1562253559
+                        );
+                    }
+
+                    if (!in_array(RequestHandlerInterface::class, class_implements($definedClass), true)) {
+                        throw new Exception(
+                            sprintf(
+                                'Request class "%s", registered in "%s", does not implement interface "%s".',
+                                $definedClass,
+                                $requestHandlersFile,
+                                RequestHandlerInterface::class
+                            ),
+                            1562257073
+                        );
+                    }
+
+                    $classes[] = $definedClass;
+                }
+            }
+        }
+
+        $this->cacheFrontend->set($cacheEntryIdentifier, $classes);
+
+        return new RequestHandlersConfiguration($classes);
+    }
+}
index d23405a..d7259c1 100644 (file)
@@ -22,6 +22,7 @@ use TYPO3\CMS\Backend\Routing\Route;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Configuration\RequestHandlersConfigurationFactory;
 use TYPO3\CMS\Extbase\Mvc\Web\Response as ExtbaseResponse;
 use TYPO3\CMS\Extbase\Persistence\ClassesConfigurationFactory;
 
@@ -84,6 +85,7 @@ class Bootstrap implements \TYPO3\CMS\Extbase\Core\BootstrapInterface
         $this->initializeObjectManager();
         $this->initializeConfiguration($configuration);
         $this->initializePersistenceClassesConfiguration();
+        $this->initializeRequestHandlersConfiguration();
         $this->initializePersistence();
     }
 
@@ -130,6 +132,16 @@ class Bootstrap implements \TYPO3\CMS\Extbase\Core\BootstrapInterface
     }
 
     /**
+     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
+     */
+    private function initializeRequestHandlersConfiguration(): void
+    {
+        $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
+        GeneralUtility::makeInstance(RequestHandlersConfigurationFactory::class, $cacheManager)
+            ->createRequestHandlersConfiguration();
+    }
+
+    /**
      * Initializes the persistence framework
      *
      * @see initialize()
index 109ede3..3001c4b 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Extbase\Mvc;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Extbase\Configuration\RequestHandlersConfigurationFactory;
+
 /**
  * Analyzes the raw request and delivers a request handler which can handle it.
  * @internal only to be used within Extbase, not part of TYPO3 Core API.
@@ -26,9 +28,9 @@ class RequestHandlerResolver
     protected $objectManager;
 
     /**
-     * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
+     * @var \TYPO3\CMS\Extbase\Configuration\RequestHandlersConfiguration
      */
-    protected $configurationManager;
+    private $requestHandlersConfiguration;
 
     /**
      * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
@@ -39,11 +41,11 @@ class RequestHandlerResolver
     }
 
     /**
-     * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
+     * @param RequestHandlersConfigurationFactory $requestHandlersConfigurationFactory
      */
-    public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
+    public function __construct(RequestHandlersConfigurationFactory $requestHandlersConfigurationFactory)
     {
-        $this->configurationManager = $configurationManager;
+        $this->requestHandlersConfiguration = $requestHandlersConfigurationFactory->createRequestHandlersConfiguration();
     }
 
     /**
@@ -55,9 +57,9 @@ class RequestHandlerResolver
      */
     public function resolveRequestHandler()
     {
-        $availableRequestHandlerClassNames = $this->getRegisteredRequestHandlerClassNames();
         $suitableRequestHandlers = [];
-        foreach ($availableRequestHandlerClassNames as $requestHandlerClassName) {
+        foreach ($this->requestHandlersConfiguration->getRegisteredRequestHandlers() as $requestHandlerClassName) {
+            /** @var RequestHandlerInterface $requestHandler */
             $requestHandler = $this->objectManager->get($requestHandlerClassName);
             if ($requestHandler->canHandleRequest()) {
                 $priority = $requestHandler->getPriority();
@@ -73,15 +75,4 @@ class RequestHandlerResolver
         ksort($suitableRequestHandlers);
         return array_pop($suitableRequestHandlers);
     }
-
-    /**
-     * Returns a list of all registered request handlers.
-     *
-     * @return array
-     */
-    public function getRegisteredRequestHandlerClassNames()
-    {
-        $settings = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
-        return is_array($settings['mvc']['requestHandlers']) ? $settings['mvc']['requestHandlers'] : [];
-    }
 }
diff --git a/typo3/sysext/extbase/Configuration/Extbase/RequestHandlers.php b/typo3/sysext/extbase/Configuration/Extbase/RequestHandlers.php
new file mode 100644 (file)
index 0000000..4bc344a
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+declare(strict_types = 1);
+
+return [
+    \TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler::class,
+    \TYPO3\CMS\Extbase\Mvc\Web\BackendRequestHandler::class,
+];
diff --git a/typo3/sysext/extbase/Tests/Functional/Configuration/RequestHandlerConfigurationFactoryTest.php b/typo3/sysext/extbase/Tests/Functional/Configuration/RequestHandlerConfigurationFactoryTest.php
new file mode 100644 (file)
index 0000000..f802bf1
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Extbase\Tests\Functional\Configuration;
+
+/*
+ * 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\Configuration\RequestHandlersConfigurationFactory;
+use TYPO3\CMS\Extbase\Mvc\Web\BackendRequestHandler;
+use TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler;
+use TYPO3\CMS\Fluid\Core\Widget\WidgetRequestHandler;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class RequestHandlerConfigurationFactoryTest extends FunctionalTestCase
+{
+    /**
+     * @var array
+     */
+    protected $coreExtensionsToLoad = ['extbase', 'fluid'];
+
+    /**
+     * @test
+     */
+    public function requestHandlerConfigurationFactoryLoadsRequestHandlersOfExtbaseAndFluid(): void
+    {
+        $configuration = (new RequestHandlersConfigurationFactory())
+            ->createRequestHandlersConfiguration();
+
+        static::assertSame(
+            [
+                FrontendRequestHandler::class,
+                BackendRequestHandler::class,
+                WidgetRequestHandler::class,
+            ],
+            $configuration->getRegisteredRequestHandlers()
+        );
+    }
+}
index 1550863..26a28d9 100644 (file)
@@ -247,63 +247,6 @@ class BackendConfigurationManagerTest extends UnitTestCase
     /**
      * @test
      */
-    public function getContextSpecificFrameworkConfigurationReturnsUnmodifiedFrameworkConfigurationIfRequestHandlersAreConfigured()
-    {
-        $frameworkConfiguration = [
-            'pluginName' => 'Pi1',
-            'extensionName' => 'SomeExtension',
-            'foo' => [
-                'bar' => [
-                    'baz' => 'Foo'
-                ]
-            ],
-            'mvc' => [
-                'requestHandlers' => [
-                    \TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler::class => 'SomeRequestHandler'
-                ]
-            ]
-        ];
-        $expectedResult = $frameworkConfiguration;
-        $actualResult = $this->backendConfigurationManager->_call('getContextSpecificFrameworkConfiguration', $frameworkConfiguration);
-        $this->assertEquals($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test
-     */
-    public function getContextSpecificFrameworkConfigurationSetsDefaultRequestHandlersIfRequestHandlersAreNotConfigured()
-    {
-        $frameworkConfiguration = [
-            'pluginName' => 'Pi1',
-            'extensionName' => 'SomeExtension',
-            'foo' => [
-                'bar' => [
-                    'baz' => 'Foo'
-                ]
-            ]
-        ];
-        $expectedResult = [
-            'pluginName' => 'Pi1',
-            'extensionName' => 'SomeExtension',
-            'foo' => [
-                'bar' => [
-                    'baz' => 'Foo'
-                ]
-            ],
-            'mvc' => [
-                'requestHandlers' => [
-                    \TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler::class => \TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler::class,
-                    \TYPO3\CMS\Extbase\Mvc\Web\BackendRequestHandler::class => \TYPO3\CMS\Extbase\Mvc\Web\BackendRequestHandler::class
-                ]
-            ]
-        ];
-        $actualResult = $this->backendConfigurationManager->_call('getContextSpecificFrameworkConfiguration', $frameworkConfiguration);
-        $this->assertEquals($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test
-     */
     public function storagePidsAreExtendedIfRecursiveSearchIsConfigured()
     {
         $storagePids = [1, 2, 3];
index 14f31a9..f4ccfdf 100644 (file)
@@ -1,9 +1,5 @@
 config.tx_extbase {
        mvc {
-               requestHandlers {
-                       TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler = TYPO3\CMS\Extbase\Mvc\Web\FrontendRequestHandler
-                       TYPO3\CMS\Extbase\Mvc\Web\BackendRequestHandler = TYPO3\CMS\Extbase\Mvc\Web\BackendRequestHandler
-               }
                throwPageNotFoundExceptionIfActionCantBeResolved = 0
        }
        persistence{
diff --git a/typo3/sysext/fluid/Configuration/Extbase/RequestHandlers.php b/typo3/sysext/fluid/Configuration/Extbase/RequestHandlers.php
new file mode 100644 (file)
index 0000000..6a061b1
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+declare(strict_types = 1);
+
+return [
+    \TYPO3\CMS\Fluid\Core\Widget\WidgetRequestHandler::class,
+];
index 80f9b27..5f403db 100644 (file)
@@ -15,11 +15,3 @@ fluidAjaxWidgetResponse {
                userFunc = TYPO3\CMS\Fluid\Core\Widget\Bootstrap->run
        }
 }
-
-config.tx_extbase {
-       mvc {
-               requestHandlers {
-                       TYPO3\CMS\Fluid\Core\Widget\WidgetRequestHandler = TYPO3\CMS\Fluid\Core\Widget\WidgetRequestHandler
-               }
-       }
-}