[TASK] Deprecate switchable controller actions 11/61511/13
authorAlexander Schnitzler <git@alexanderschnitzler.de>
Mon, 21 Oct 2019 10:13:34 +0000 (12:13 +0200)
committerSusanne Moog <look@susi.dev>
Fri, 6 Dec 2019 14:39:53 +0000 (15:39 +0100)
The usage of switchable controller actions, both via
flexforms and typoscript, is deprecated and will be
removed in one the next major versions of TYPO3,
probably version 11.0 or 12.0.

Switchable controller actions allowed to override the
php plugin configuration and to create god plugins, i.e.
plugins that can be set into multiple different modes
and therefore take care of all possible use cases.

Every plugin should serve a single purpose, therefore
the usage of switchable controller actions is an anti
pattern which will be removed.

The switchable controller action mechanic will be
removed without replacement which means, that there
is no migration path to a similar feature.

Instead, extension authors need to create multiple,
dedicated plugins for different use cases.

Releases: master
Resolves: #89463
Change-Id: I41afac9303205f97f390f208803908177e00cda5
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/61511
Tested-by: Richard Haeser <richard@maxserv.com>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Susanne Moog <look@susi.dev>
Reviewed-by: Richard Haeser <richard@maxserv.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Susanne Moog <look@susi.dev>
typo3/sysext/core/Documentation/Changelog/master/Deprecation-89463-DeprecateSwitchableControllerActions.rst [new file with mode: 0644]
typo3/sysext/extbase/Classes/Configuration/AbstractConfigurationManager.php
typo3/sysext/extbase/Classes/Configuration/FrontendConfigurationManager.php
typo3/sysext/extbase/Classes/Hook/DataHandler/CheckFlexFormValue.php
typo3/sysext/extbase/Tests/Unit/Configuration/AbstractConfigurationManagerTest.php
typo3/sysext/extbase/Tests/Unit/Configuration/BackendConfigurationManagerTest.php
typo3/sysext/extbase/Tests/Unit/Configuration/FrontendConfigurationManagerTest.php
typo3/sysext/extbase/Tests/UnitDeprecated/Configuration/AbstractConfigurationManagerTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/UnitDeprecated/Configuration/FrontendConfigurationManagerTest.php [new file with mode: 0644]

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-89463-DeprecateSwitchableControllerActions.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-89463-DeprecateSwitchableControllerActions.rst
new file mode 100644 (file)
index 0000000..6666c64
--- /dev/null
@@ -0,0 +1,94 @@
+.. include:: ../../Includes.txt
+
+===============================================================
+Deprecation: #89463 - Deprecate `switchable controller actions`
+===============================================================
+
+See :issue:`89463`
+
+Description
+===========
+
+`Switchable controller actions` have been marked as deprecated and will be removed in one of the next major versions
+of TYPO3, probably version 11.0 or 12.0.
+
+`Switchable controller actions` are used to override the allowed set of controllers and actions via typoscript or plugin
+flexforms. While this is convenient for reusing the same plugin for a lot of different use cases, it's also very
+problematic as it completely overrides the original configuration defined via
+:php:`\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin`.
+
+`Switchable controller actions` therefore have bad implications that rectify their removal.
+
+First of all, `switchable controller actions` override the original configuration of plugins at runtime and possibly
+depending on conditions which contradicts the idea of :php:`\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin`
+being the authoritative way to define configuration.
+
+Using the same plugin as an entry point for many different functionalities contradicts the idea of a plugin serving one
+specific purpose. `Switchable controller actions` allow for creating one central plugin that takes care of everything.
+
+
+Impact
+======
+
+All plugins that are using `switchable controller actions` need to be split into multiple different plugins. Usually, one
+would create a new plugin for each possible `switchable controller actions` configuration entry.
+
+
+Affected Installations
+======================
+
+All installations that make use of `switchable controller actions`, either via flexform configuration of plugins or via
+typoscript configuration.
+
+
+Migration
+=========
+
+Unfortunately an automatic migration is not possible. As `switchable controller actions` allowed to override the whole
+configuration of allowed controllers and actions, the only way to migrate is to create dedicated plugins for each former
+`switchable controller actions` configuration entry.
+
+Example:
+
+.. code-block:: xml
+
+   <switchableControllerActions>
+      <TCEforms>
+         <label>switchable controller actions</label>
+         <config>
+            <renderType>selectSingle</renderType>
+            <items>
+               <numIndex index="1">
+                  <numIndex index="0">List</numIndex>
+                  <numIndex index="1">Product->list</numIndex>
+               </numIndex>
+               <numIndex index="2">
+                  <numIndex index="0">Show</numIndex>
+                  <numIndex index="1">Product->show</numIndex>
+               </numIndex>
+            </items>
+         </config>
+      </TCEforms>
+   </switchableControllerActions>
+
+This configuration would lead to the creation configuration of two different plugins like this:
+
+.. code-block: php
+
+    \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
+        'extension',
+        'list',
+        [
+            'Product' => 'list'
+        ]
+    );
+
+    \TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
+        'extension',
+        'show',
+        [
+            'Product' => 'show'
+        ]
+    );
+
+.. index:: FlexForm, PHP-API, TypoScript, NotScanned, ext:extbase
index c813fa7..5d7a58a 100644 (file)
@@ -241,9 +241,19 @@ abstract class AbstractConfigurationManager implements \TYPO3\CMS\Core\Singleton
      *
      * @param array &$frameworkConfiguration
      * @param array $switchableControllerActions
+     * @deprecated since TYPO3 v10, will be removed in one of the next major versions of TYPO3, probably version 11.0 or 12.0.
      */
     protected function overrideControllerConfigurationWithSwitchableControllerActions(array &$frameworkConfiguration, array $switchableControllerActions): void
     {
+        trigger_error(
+            sprintf(
+                'Plugin "%s" of extension "%s" uses switchable controller actions which has been marked as deprecated as of version TYPO3 10 and will be removed in one of the next major versions of TYPO3, probably version 11.0 or 12.0',
+                $frameworkConfiguration['pluginName'],
+                $frameworkConfiguration['extensionName']
+            ),
+            E_USER_DEPRECATED
+        );
+
         $controllerAliasToClass = [];
         foreach ($frameworkConfiguration['controllerConfiguration'] as $controllerClass => $controllerConfiguration) {
             $controllerAliasToClass[$controllerConfiguration['alias']] = $controllerClass;
index 94c5eff..73e1da8 100644 (file)
@@ -230,6 +230,7 @@ class FrontendConfigurationManager extends \TYPO3\CMS\Extbase\Configuration\Abst
      * @param array $flexFormConfiguration The full flexForm configuration
      * @throws Exception\ParseErrorException
      * @return array the modified framework configuration, if needed
+     * @deprecated since TYPO3 v10, will be removed in one of the next major versions of TYPO3, probably version 11.0 or 12.0.
      */
     protected function overrideControllerConfigurationWithSwitchableControllerActionsFromFlexForm(array $frameworkConfiguration, array $flexFormConfiguration): array
     {
index 838dc6a..b058846 100644 (file)
@@ -18,6 +18,7 @@ use TYPO3\CMS\Core\DataHandling\DataHandler;
 
 /**
  * @internal this is not part of TYPO3 Core API as it is a concrete hook implementation.
+ * @deprecated since TYPO3 v10, will be removed when support for switchable controller actions is removed
  */
 class CheckFlexFormValue
 {
index 5447d59..87e5909 100644 (file)
@@ -103,21 +103,6 @@ class AbstractConfigurationManagerTest extends UnitTestCase
     ];
 
     /**
-     * @var array
-     */
-    protected $testSwitchableControllerActions = [
-        'MyExtension\\Controller\\Controller1' => [
-            'alias' => 'Controller1',
-            'actions' => ['action1', 'action2', 'action3']
-        ],
-        'MyExtension\\Controller\\Controller2' => [
-            'alias' => 'Controller2',
-            'actions' => ['action4', 'action5', 'action6'],
-            'nonCacheableActions' => ['action4', 'action6']
-        ]
-    ];
-
-    /**
      * Sets up this testcase
      */
     protected function setUp(): void
@@ -388,301 +373,6 @@ class AbstractConfigurationManagerTest extends UnitTestCase
     }
 
     /**
-     * switchableControllerActions *
-     */
-    /**
-     * @test
-     */
-    public function switchableControllerActionsAreNotOverriddenIfPluginNameIsSpecified(): void
-    {
-        /** @var AbstractConfigurationManager|\PHPUnit\Framework\MockObject\MockObject|AccessibleObjectInterface $abstractConfigurationManager */
-        $abstractConfigurationManager = $this->getAccessibleMock(
-            AbstractConfigurationManager::class,
-            [
-                'overrideControllerConfigurationWithSwitchableControllerActions',
-                'getContextSpecificFrameworkConfiguration',
-                'getTypoScriptSetup',
-                'getPluginConfiguration',
-                'getControllerConfiguration',
-                'getRecursiveStoragePids'
-            ],
-            [],
-            '',
-            false
-        );
-        $abstractConfigurationManager->_set('typoScriptService', $this->mockTypoScriptService);
-        $abstractConfigurationManager->setConfiguration(['switchableControllerActions' => ['overriddenSwitchableControllerActions']]);
-        $abstractConfigurationManager->expects(self::any())->method('getPluginConfiguration')->willReturn([]);
-        $abstractConfigurationManager->expects(self::never())->method('overrideControllerConfigurationWithSwitchableControllerActions');
-        $abstractConfigurationManager->getConfiguration('SomeExtensionName', 'SomePluginName');
-    }
-
-    /**
-     * @test
-     */
-    public function switchableControllerActionsAreOverriddenIfSpecifiedPluginIsTheCurrentPlugin(): void
-    {
-        /** @var AbstractConfigurationManager|\PHPUnit\Framework\MockObject\MockObject|AccessibleObjectInterface $abstractConfigurationManager */
-        $configuration = [
-            'extensionName' => 'CurrentExtensionName',
-            'pluginName' => 'CurrentPluginName',
-            'switchableControllerActions' => ['overriddenSwitchableControllerActions']
-        ];
-        $abstractConfigurationManager = $this->getAccessibleMock(
-            AbstractConfigurationManager::class,
-            [
-                'overrideControllerConfigurationWithSwitchableControllerActions',
-                'getContextSpecificFrameworkConfiguration',
-                'getTypoScriptSetup',
-                'getPluginConfiguration',
-                'getControllerConfiguration',
-                'getRecursiveStoragePids'
-            ],
-            [],
-            '',
-            false
-        );
-        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
-        $abstractConfigurationManager->_set('typoScriptService', $this->mockTypoScriptService);
-        $abstractConfigurationManager->setConfiguration($configuration);
-        $abstractConfigurationManager->expects(self::any())->method('getPluginConfiguration')->willReturn([]);
-        $abstractConfigurationManager->expects(self::once())->method('overrideControllerConfigurationWithSwitchableControllerActions');
-        $abstractConfigurationManager->getConfiguration('CurrentExtensionName', 'CurrentPluginName');
-    }
-
-    /**
-     * @test
-     */
-    public function switchableControllerActionsAreOverriddenIfPluginNameIsNotSpecified(): void
-    {
-        /** @var AbstractConfigurationManager|\PHPUnit\Framework\MockObject\MockObject|AccessibleObjectInterface $abstractConfigurationManager */
-        $configuration = ['switchableControllerActions' => ['overriddenSwitchableControllerActions']];
-        $abstractConfigurationManager = $this->getAccessibleMock(
-            AbstractConfigurationManager::class,
-            [
-                'overrideControllerConfigurationWithSwitchableControllerActions',
-                'getContextSpecificFrameworkConfiguration',
-                'getTypoScriptSetup',
-                'getPluginConfiguration',
-                'getControllerConfiguration',
-                'getRecursiveStoragePids'
-            ],
-            [],
-            '',
-            false
-        );
-        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
-        $abstractConfigurationManager->_set('typoScriptService', $this->mockTypoScriptService);
-        $abstractConfigurationManager->setConfiguration($configuration);
-        $abstractConfigurationManager->expects(self::any())->method('getPluginConfiguration')->willReturn([]);
-        $abstractConfigurationManager->expects(self::once())->method('overrideControllerConfigurationWithSwitchableControllerActions');
-        $abstractConfigurationManager->getConfiguration();
-    }
-
-    /**
-     * @test
-     */
-    public function orderOfActionsCanBeOverriddenForCurrentPlugin(): void
-    {
-        $configuration = [
-            'extensionName' => 'CurrentExtensionName',
-            'pluginName' => 'CurrentPluginName',
-            'switchableControllerActions' => [
-                'Controller1' => ['action2', 'action1', 'action3']
-            ]
-        ];
-        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
-        $this->abstractConfigurationManager->setConfiguration($configuration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testPluginConfiguration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testSwitchableControllerActions);
-        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
-            $a
-        ) {
-            return $a;
-        });
-        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
-        $expectedResult = [
-            'MyExtension\\Controller\\Controller1' => [
-                'className' => 'MyExtension\\Controller\\Controller1',
-                'alias' => 'Controller1',
-                'actions' => ['action2', 'action1', 'action3']
-            ]
-        ];
-        $actualResult = $mergedConfiguration['controllerConfiguration'];
-        self::assertEquals($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test
-     */
-    public function controllerOfSwitchableControllerActionsCanBeAFullyQualifiedClassName(): void
-    {
-        $configuration = [
-            'extensionName' => 'CurrentExtensionName',
-            'pluginName' => 'CurrentPluginName',
-            'switchableControllerActions' => [
-                'MyExtension\\Controller\\Controller1' => ['action2', 'action1', 'action3'],
-                '\\MyExtension\\Controller\\Controller2' => ['newAction2', 'action4', 'action5']
-            ]
-        ];
-        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
-        $this->abstractConfigurationManager->setConfiguration($configuration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testPluginConfiguration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testSwitchableControllerActions);
-        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
-            $a
-        ) {
-            return $a;
-        });
-        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
-        $expectedResult = [
-            'MyExtension\\Controller\\Controller1' => [
-                'className' => 'MyExtension\\Controller\\Controller1',
-                'alias' => 'Controller1',
-                'actions' => ['action2', 'action1', 'action3']
-            ],
-            'MyExtension\\Controller\\Controller2' => [
-                'className' => 'MyExtension\\Controller\\Controller2',
-                'alias' => 'Controller2',
-                'actions' => ['newAction2', 'action4', 'action5'],
-                'nonCacheableActions' => ['action4']
-            ]
-        ];
-        $actualResult = $mergedConfiguration['controllerConfiguration'];
-        self::assertEquals($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test
-     */
-    public function newActionsCanBeAddedForCurrentPlugin(): void
-    {
-        $configuration = [
-            'extensionName' => 'CurrentExtensionName',
-            'pluginName' => 'CurrentPluginName',
-            'switchableControllerActions' => [
-                'Controller1' => ['action2', 'action1', 'action3', 'newAction']
-            ]
-        ];
-        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
-        $this->abstractConfigurationManager->setConfiguration($configuration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testPluginConfiguration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testSwitchableControllerActions);
-        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
-            $a
-        ) {
-            return $a;
-        });
-        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
-        $expectedResult = [
-            'MyExtension\\Controller\\Controller1' => [
-                'className' => 'MyExtension\\Controller\\Controller1',
-                'alias' => 'Controller1',
-                'actions' => ['action2', 'action1', 'action3', 'newAction']
-            ]
-        ];
-        $actualResult = $mergedConfiguration['controllerConfiguration'];
-        self::assertEquals($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test
-     */
-    public function controllersCanNotBeOverridden(): void
-    {
-        $configuration = [
-            'extensionName' => 'CurrentExtensionName',
-            'pluginName' => 'CurrentPluginName',
-            'switchableControllerActions' => [
-                'NewController' => ['action1', 'action2']
-            ]
-        ];
-        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
-        $this->abstractConfigurationManager->setConfiguration($configuration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testPluginConfiguration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testSwitchableControllerActions);
-        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
-            $a
-        ) {
-            return $a;
-        });
-        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
-        $expectedResult = [];
-        $actualResult = $mergedConfiguration['controllerConfiguration'];
-        self::assertEquals($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test
-     */
-    public function cachingOfActionsCanNotBeChanged(): void
-    {
-        $configuration = [
-            'extensionName' => 'CurrentExtensionName',
-            'pluginName' => 'CurrentPluginName',
-            'switchableControllerActions' => [
-                'Controller1' => ['newAction', 'action1'],
-                'Controller2' => ['newAction2', 'action4', 'action5']
-            ]
-        ];
-        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
-        $this->abstractConfigurationManager->setConfiguration($configuration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testPluginConfiguration);
-        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
-            'CurrentExtensionName',
-            'CurrentPluginName'
-        )->willReturn($this->testSwitchableControllerActions);
-        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
-            $a
-        ) {
-            return $a;
-        });
-        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
-        $expectedResult = [
-            'MyExtension\\Controller\\Controller1' => [
-                'className' => 'MyExtension\\Controller\\Controller1',
-                'alias' => 'Controller1',
-                'actions' => ['newAction', 'action1']
-            ],
-            'MyExtension\\Controller\\Controller2' => [
-                'className' => 'MyExtension\\Controller\\Controller2',
-                'alias' => 'Controller2',
-                'actions' => ['newAction2', 'action4', 'action5'],
-                'nonCacheableActions' => ['action4']
-            ]
-        ];
-        $actualResult = $mergedConfiguration['controllerConfiguration'];
-        self::assertEquals($expectedResult, $actualResult);
-    }
-
-    /**
      * @test
      */
     public function getContentObjectReturnsNullIfNoContentObjectHasBeenSet(): void
index fe64bc9..9e7aa6b 100644 (file)
@@ -259,7 +259,7 @@ class BackendConfigurationManagerTest extends UnitTestCase
 
         $abstractConfigurationManager = $this->getAccessibleMock(
             \TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager::class,
-            ['overrideControllerConfigurationWithSwitchableControllerActions', 'getContextSpecificFrameworkConfiguration', 'getTypoScriptSetup', 'getPluginConfiguration', 'getControllerConfiguration'],
+            ['getContextSpecificFrameworkConfiguration', 'getTypoScriptSetup', 'getPluginConfiguration', 'getControllerConfiguration'],
             [],
             '',
             false
@@ -290,7 +290,7 @@ class BackendConfigurationManagerTest extends UnitTestCase
 
         $abstractConfigurationManager = $this->getAccessibleMock(
             \TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager::class,
-            ['overrideControllerConfigurationWithSwitchableControllerActions', 'getContextSpecificFrameworkConfiguration', 'getTypoScriptSetup', 'getPluginConfiguration', 'getControllerConfiguration', 'getQueryGenerator'],
+            ['getContextSpecificFrameworkConfiguration', 'getTypoScriptSetup', 'getPluginConfiguration', 'getControllerConfiguration', 'getQueryGenerator'],
             [],
             '',
             false
@@ -315,7 +315,7 @@ class BackendConfigurationManagerTest extends UnitTestCase
 
         $abstractConfigurationManager = $this->getAccessibleMock(
             \TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager::class,
-            ['overrideControllerConfigurationWithSwitchableControllerActions', 'getContextSpecificFrameworkConfiguration', 'getTypoScriptSetup', 'getPluginConfiguration', 'getControllerConfiguration'],
+            ['getContextSpecificFrameworkConfiguration', 'getTypoScriptSetup', 'getPluginConfiguration', 'getControllerConfiguration'],
             [],
             '',
             false
@@ -336,7 +336,7 @@ class BackendConfigurationManagerTest extends UnitTestCase
 
         $abstractConfigurationManager = $this->getAccessibleMock(
             \TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager::class,
-            ['overrideControllerConfigurationWithSwitchableControllerActions', 'getContextSpecificFrameworkConfiguration', 'getTypoScriptSetup', 'getPluginConfiguration', 'getControllerConfiguration'],
+            ['getContextSpecificFrameworkConfiguration', 'getTypoScriptSetup', 'getPluginConfiguration', 'getControllerConfiguration'],
             [],
             '',
             false
index 22e8325..098c12f 100644 (file)
@@ -18,7 +18,6 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Configuration;
 
 use TYPO3\CMS\Core\Service\FlexFormService;
 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
-use TYPO3\CMS\Extbase\Configuration\Exception\ParseErrorException;
 use TYPO3\CMS\Extbase\Configuration\FrontendConfigurationManager;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
@@ -270,113 +269,6 @@ class FrontendConfigurationManagerTest extends UnitTestCase
     /**
      * @test
      */
-    public function overrideControllerConfigurationWithSwitchableControllerActionsFromFlexFormReturnsUnchangedFrameworkConfigurationIfNoFlexFormConfigurationIsFound(
-    ): void {
-        $frameworkConfiguration = [
-            'pluginName' => 'Pi1',
-            'extensionName' => 'SomeExtension',
-            'controllerConfiguration' => [
-                'Controller1' => [
-                    'controller' => 'Controller1',
-                    'actions' => 'action1 , action2'
-                ],
-                'Controller2' => [
-                    'controller' => 'Controller2',
-                    'actions' => 'action2 , action1,action3',
-                    'nonCacheableActions' => 'action2, action3'
-                ]
-            ]
-        ];
-        $flexFormConfiguration = [];
-        $actualResult = $this->frontendConfigurationManager->_call(
-            'overrideControllerConfigurationWithSwitchableControllerActionsFromFlexForm',
-            $frameworkConfiguration,
-            $flexFormConfiguration
-        );
-        self::assertSame($frameworkConfiguration, $actualResult);
-    }
-
-    /**
-     * @test
-     */
-    public function overrideControllerConfigurationWithSwitchableControllerActionsFromFlexFormMergesNonCacheableActions(): void
-    {
-        $frameworkConfiguration = [
-            'pluginName' => 'Pi1',
-            'extensionName' => 'SomeExtension',
-            'controllerConfiguration' => [
-                'MyExtension\\Controller\\Controller1' => [
-                    'alias' => 'Controller1',
-                    'actions' => ['action1 , action2']
-                ],
-                'MyExtension\\Controller\\Controller2' => [
-                    'alias' => 'Controller2',
-                    'actions' => ['action2', 'action1', 'action3'],
-                    'nonCacheableActions' => ['action2', 'action3']
-                ]
-            ]
-        ];
-        $flexFormConfiguration = [
-            'switchableControllerActions' => 'Controller1  -> action2;\\MyExtension\\Controller\\Controller2->action3;  Controller2->action1'
-        ];
-        $expectedResult = [
-            'pluginName' => 'Pi1',
-            'extensionName' => 'SomeExtension',
-            'controllerConfiguration' => [
-                'MyExtension\\Controller\\Controller1' => [
-                    'className' => 'MyExtension\\Controller\\Controller1',
-                    'alias' => 'Controller1',
-                    'actions' => ['action2']
-                ],
-                'MyExtension\\Controller\\Controller2' => [
-                    'className' => 'MyExtension\\Controller\\Controller2',
-                    'alias' => 'Controller2',
-                    'actions' => ['action3', 'action1'],
-                    'nonCacheableActions' => [1 => 'action3']
-                ]
-            ]
-        ];
-        $actualResult = $this->frontendConfigurationManager->_call(
-            'overrideControllerConfigurationWithSwitchableControllerActionsFromFlexForm',
-            $frameworkConfiguration,
-            $flexFormConfiguration
-        );
-        self::assertEquals($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test
-     */
-    public function overrideControllerConfigurationWithSwitchableControllerActionsThrowsExceptionIfFlexFormConfigurationIsInvalid(): void
-    {
-        $this->expectException(ParseErrorException::class);
-        $this->expectExceptionCode(1257146403);
-        $frameworkConfiguration = [
-            'pluginName' => 'Pi1',
-            'extensionName' => 'SomeExtension',
-            'controllerConfiguration' => [
-                'Controller1' => [
-                    'actions' => ['action1 , action2']
-                ],
-                'Controller2' => [
-                    'actions' => ['action2', 'action1', 'action3'],
-                    'nonCacheableActions' => ['action2', 'action3']
-                ]
-            ]
-        ];
-        $flexFormConfiguration = [
-            'switchableControllerActions' => 'Controller1->;Controller2->action3;Controller2->action1'
-        ];
-        $this->frontendConfigurationManager->_call(
-            'overrideControllerConfigurationWithSwitchableControllerActionsFromFlexForm',
-            $frameworkConfiguration,
-            $flexFormConfiguration
-        );
-    }
-
-    /**
-     * @test
-     */
     public function getContextSpecificFrameworkConfigurationCorrectlyCallsOverrideMethods(): void
     {
         $frameworkConfiguration = [
@@ -418,7 +310,6 @@ class FrontendConfigurationManagerTest extends UnitTestCase
         $abstractConfigurationManager = $this->getAccessibleMock(
             FrontendConfigurationManager::class,
             [
-                'overrideControllerConfigurationWithSwitchableControllerActions',
                 'getContextSpecificFrameworkConfiguration',
                 'getTypoScriptSetup',
                 'getPluginConfiguration',
@@ -451,7 +342,6 @@ class FrontendConfigurationManagerTest extends UnitTestCase
         $abstractConfigurationManager = $this->getAccessibleMock(
             FrontendConfigurationManager::class,
             [
-                'overrideControllerConfigurationWithSwitchableControllerActions',
                 'getContextSpecificFrameworkConfiguration',
                 'getTypoScriptSetup',
                 'getPluginConfiguration',
@@ -484,7 +374,6 @@ class FrontendConfigurationManagerTest extends UnitTestCase
         $abstractConfigurationManager = $this->getAccessibleMock(
             FrontendConfigurationManager::class,
             [
-                'overrideControllerConfigurationWithSwitchableControllerActions',
                 'getContextSpecificFrameworkConfiguration',
                 'getTypoScriptSetup',
                 'getPluginConfiguration',
@@ -515,7 +404,6 @@ class FrontendConfigurationManagerTest extends UnitTestCase
         $abstractConfigurationManager = $this->getAccessibleMock(
             FrontendConfigurationManager::class,
             [
-                'overrideControllerConfigurationWithSwitchableControllerActions',
                 'getContextSpecificFrameworkConfiguration',
                 'getTypoScriptSetup',
                 'getPluginConfiguration',
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Configuration/AbstractConfigurationManagerTest.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Configuration/AbstractConfigurationManagerTest.php
new file mode 100644 (file)
index 0000000..888fe2f
--- /dev/null
@@ -0,0 +1,385 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\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 PHPUnit\Framework\MockObject\MockObject;
+use TYPO3\CMS\Core\TypoScript\TypoScriptService;
+use TYPO3\CMS\Extbase\Configuration\AbstractConfigurationManager;
+use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class AbstractConfigurationManagerTest extends UnitTestCase
+{
+    /**
+     * @var AbstractConfigurationManager|MockObject|AccessibleObjectInterface
+     */
+    protected $abstractConfigurationManager;
+
+    /**
+     * @var TypoScriptService|MockObject|AccessibleObjectInterface
+     */
+    protected $mockTypoScriptService;
+
+    /**
+     * @var array
+     */
+    protected $testPluginConfiguration = [
+        'settings' => [
+            'setting1' => 'overriddenValue1',
+            'setting3' => 'additionalValue'
+        ],
+        'view' => [
+            'viewSub' => [
+                'key1' => 'overridden',
+                'key3' => 'new key'
+            ]
+        ],
+        'persistence' => [
+            'storagePid' => '123'
+        ]
+    ];
+
+    /**
+     * @var array
+     */
+    protected $testSwitchableControllerActions = [
+        'MyExtension\\Controller\\Controller1' => [
+            'alias' => 'Controller1',
+            'actions' => ['action1', 'action2', 'action3']
+        ],
+        'MyExtension\\Controller\\Controller2' => [
+            'alias' => 'Controller2',
+            'actions' => ['action4', 'action5', 'action6'],
+            'nonCacheableActions' => ['action4', 'action6']
+        ]
+    ];
+
+    /**
+     * Sets up this testcase
+     */
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->abstractConfigurationManager = $this->getAccessibleMock(
+            AbstractConfigurationManager::class,
+            [
+                'getContextSpecificFrameworkConfiguration',
+                'getTypoScriptSetup',
+                'getPluginConfiguration',
+                'getControllerConfiguration',
+                'getRecursiveStoragePids'
+            ],
+            [],
+            '',
+            false
+        );
+        $this->mockTypoScriptService = $this->getAccessibleMock(TypoScriptService::class);
+        $this->abstractConfigurationManager->_set('typoScriptService', $this->mockTypoScriptService);
+    }
+
+    /**
+     * @test
+     */
+    public function orderOfActionsCanBeOverriddenForCurrentPlugin(): void
+    {
+        $configuration = [
+            'extensionName' => 'CurrentExtensionName',
+            'pluginName' => 'CurrentPluginName',
+            'switchableControllerActions' => [
+                'Controller1' => ['action2', 'action1', 'action3']
+            ]
+        ];
+        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
+        $this->abstractConfigurationManager->setConfiguration($configuration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testPluginConfiguration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testSwitchableControllerActions);
+        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
+            $a
+        ) {
+            return $a;
+        });
+        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
+        $expectedResult = [
+            'MyExtension\\Controller\\Controller1' => [
+                'className' => 'MyExtension\\Controller\\Controller1',
+                'alias' => 'Controller1',
+                'actions' => ['action2', 'action1', 'action3']
+            ]
+        ];
+        $actualResult = $mergedConfiguration['controllerConfiguration'];
+        self::assertEquals($expectedResult, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function controllerOfSwitchableControllerActionsCanBeAFullyQualifiedClassName(): void
+    {
+        $configuration = [
+            'extensionName' => 'CurrentExtensionName',
+            'pluginName' => 'CurrentPluginName',
+            'switchableControllerActions' => [
+                'MyExtension\\Controller\\Controller1' => ['action2', 'action1', 'action3'],
+                '\\MyExtension\\Controller\\Controller2' => ['newAction2', 'action4', 'action5']
+            ]
+        ];
+        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
+        $this->abstractConfigurationManager->setConfiguration($configuration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testPluginConfiguration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testSwitchableControllerActions);
+        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
+            $a
+        ) {
+            return $a;
+        });
+        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
+        $expectedResult = [
+            'MyExtension\\Controller\\Controller1' => [
+                'className' => 'MyExtension\\Controller\\Controller1',
+                'alias' => 'Controller1',
+                'actions' => ['action2', 'action1', 'action3']
+            ],
+            'MyExtension\\Controller\\Controller2' => [
+                'className' => 'MyExtension\\Controller\\Controller2',
+                'alias' => 'Controller2',
+                'actions' => ['newAction2', 'action4', 'action5'],
+                'nonCacheableActions' => ['action4']
+            ]
+        ];
+        $actualResult = $mergedConfiguration['controllerConfiguration'];
+        self::assertEquals($expectedResult, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function newActionsCanBeAddedForCurrentPlugin(): void
+    {
+        $configuration = [
+            'extensionName' => 'CurrentExtensionName',
+            'pluginName' => 'CurrentPluginName',
+            'switchableControllerActions' => [
+                'Controller1' => ['action2', 'action1', 'action3', 'newAction']
+            ]
+        ];
+        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
+        $this->abstractConfigurationManager->setConfiguration($configuration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testPluginConfiguration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testSwitchableControllerActions);
+        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
+            $a
+        ) {
+            return $a;
+        });
+        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
+        $expectedResult = [
+            'MyExtension\\Controller\\Controller1' => [
+                'className' => 'MyExtension\\Controller\\Controller1',
+                'alias' => 'Controller1',
+                'actions' => ['action2', 'action1', 'action3', 'newAction']
+            ]
+        ];
+        $actualResult = $mergedConfiguration['controllerConfiguration'];
+        self::assertEquals($expectedResult, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function controllersCanNotBeOverridden(): void
+    {
+        $configuration = [
+            'extensionName' => 'CurrentExtensionName',
+            'pluginName' => 'CurrentPluginName',
+            'switchableControllerActions' => [
+                'NewController' => ['action1', 'action2']
+            ]
+        ];
+        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
+        $this->abstractConfigurationManager->setConfiguration($configuration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testPluginConfiguration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testSwitchableControllerActions);
+        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
+            $a
+        ) {
+            return $a;
+        });
+        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
+        $expectedResult = [];
+        $actualResult = $mergedConfiguration['controllerConfiguration'];
+        self::assertEquals($expectedResult, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function cachingOfActionsCanNotBeChanged(): void
+    {
+        $configuration = [
+            'extensionName' => 'CurrentExtensionName',
+            'pluginName' => 'CurrentPluginName',
+            'switchableControllerActions' => [
+                'Controller1' => ['newAction', 'action1'],
+                'Controller2' => ['newAction2', 'action4', 'action5']
+            ]
+        ];
+        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
+        $this->abstractConfigurationManager->setConfiguration($configuration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getPluginConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testPluginConfiguration);
+        $this->abstractConfigurationManager->expects(self::once())->method('getControllerConfiguration')->with(
+            'CurrentExtensionName',
+            'CurrentPluginName'
+        )->willReturn($this->testSwitchableControllerActions);
+        $this->abstractConfigurationManager->expects(self::once())->method('getContextSpecificFrameworkConfiguration')->willReturnCallback(function (
+            $a
+        ) {
+            return $a;
+        });
+        $mergedConfiguration = $this->abstractConfigurationManager->getConfiguration();
+        $expectedResult = [
+            'MyExtension\\Controller\\Controller1' => [
+                'className' => 'MyExtension\\Controller\\Controller1',
+                'alias' => 'Controller1',
+                'actions' => ['newAction', 'action1']
+            ],
+            'MyExtension\\Controller\\Controller2' => [
+                'className' => 'MyExtension\\Controller\\Controller2',
+                'alias' => 'Controller2',
+                'actions' => ['newAction2', 'action4', 'action5'],
+                'nonCacheableActions' => ['action4']
+            ]
+        ];
+        $actualResult = $mergedConfiguration['controllerConfiguration'];
+        self::assertEquals($expectedResult, $actualResult);
+    }
+
+    /**
+    * @test
+    */
+    public function switchableControllerActionsAreNotOverriddenIfPluginNameIsSpecified(): void
+    {
+        /** @var AbstractConfigurationManager|MockObject|AccessibleObjectInterface $abstractConfigurationManager */
+        $abstractConfigurationManager = $this->getAccessibleMock(
+            AbstractConfigurationManager::class,
+            [
+                'overrideControllerConfigurationWithSwitchableControllerActions',
+                'getContextSpecificFrameworkConfiguration',
+                'getTypoScriptSetup',
+                'getPluginConfiguration',
+                'getControllerConfiguration',
+                'getRecursiveStoragePids'
+            ],
+            [],
+            '',
+            false
+        );
+        $abstractConfigurationManager->_set('typoScriptService', $this->mockTypoScriptService);
+        $abstractConfigurationManager->setConfiguration(['switchableControllerActions' => ['overriddenSwitchableControllerActions']]);
+        $abstractConfigurationManager->expects(self::any())->method('getPluginConfiguration')->willReturn([]);
+        $abstractConfigurationManager->expects(self::never())->method('overrideControllerConfigurationWithSwitchableControllerActions');
+        $abstractConfigurationManager->getConfiguration('SomeExtensionName', 'SomePluginName');
+    }
+
+    /**
+     * @test
+     */
+    public function switchableControllerActionsAreOverriddenIfSpecifiedPluginIsTheCurrentPlugin(): void
+    {
+        /** @var AbstractConfigurationManager|MockObject|AccessibleObjectInterface $abstractConfigurationManager */
+        $configuration = [
+            'extensionName' => 'CurrentExtensionName',
+            'pluginName' => 'CurrentPluginName',
+            'switchableControllerActions' => ['overriddenSwitchableControllerActions']
+        ];
+        $abstractConfigurationManager = $this->getAccessibleMock(
+            AbstractConfigurationManager::class,
+            [
+                'overrideControllerConfigurationWithSwitchableControllerActions',
+                'getContextSpecificFrameworkConfiguration',
+                'getTypoScriptSetup',
+                'getPluginConfiguration',
+                'getControllerConfiguration',
+                'getRecursiveStoragePids'
+            ],
+            [],
+            '',
+            false
+        );
+        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
+        $abstractConfigurationManager->_set('typoScriptService', $this->mockTypoScriptService);
+        $abstractConfigurationManager->setConfiguration($configuration);
+        $abstractConfigurationManager->expects(self::any())->method('getPluginConfiguration')->willReturn([]);
+        $abstractConfigurationManager->expects(self::once())->method('overrideControllerConfigurationWithSwitchableControllerActions');
+        $abstractConfigurationManager->getConfiguration('CurrentExtensionName', 'CurrentPluginName');
+    }
+
+    /**
+     * @test
+     */
+    public function switchableControllerActionsAreOverriddenIfPluginNameIsNotSpecified(): void
+    {
+        /** @var AbstractConfigurationManager|MockObject|AccessibleObjectInterface $abstractConfigurationManager */
+        $configuration = ['switchableControllerActions' => ['overriddenSwitchableControllerActions']];
+        $abstractConfigurationManager = $this->getAccessibleMock(
+            AbstractConfigurationManager::class,
+            [
+                'overrideControllerConfigurationWithSwitchableControllerActions',
+                'getContextSpecificFrameworkConfiguration',
+                'getTypoScriptSetup',
+                'getPluginConfiguration',
+                'getControllerConfiguration',
+                'getRecursiveStoragePids'
+            ],
+            [],
+            '',
+            false
+        );
+        $this->mockTypoScriptService->expects(self::any())->method('convertTypoScriptArrayToPlainArray')->with($configuration)->willReturn($configuration);
+        $abstractConfigurationManager->_set('typoScriptService', $this->mockTypoScriptService);
+        $abstractConfigurationManager->setConfiguration($configuration);
+        $abstractConfigurationManager->expects(self::any())->method('getPluginConfiguration')->willReturn([]);
+        $abstractConfigurationManager->expects(self::once())->method('overrideControllerConfigurationWithSwitchableControllerActions');
+        $abstractConfigurationManager->getConfiguration();
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/UnitDeprecated/Configuration/FrontendConfigurationManagerTest.php b/typo3/sysext/extbase/Tests/UnitDeprecated/Configuration/FrontendConfigurationManagerTest.php
new file mode 100644 (file)
index 0000000..d4633d2
--- /dev/null
@@ -0,0 +1,170 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\UnitDeprecated\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 PHPUnit\Framework\MockObject\MockObject;
+use TYPO3\CMS\Core\TypoScript\TypoScriptService;
+use TYPO3\CMS\Extbase\Configuration\Exception\ParseErrorException;
+use TYPO3\CMS\Extbase\Configuration\FrontendConfigurationManager;
+use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
+use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class FrontendConfigurationManagerTest extends UnitTestCase
+{
+    /**
+     * @var ContentObjectRenderer|MockObject
+     */
+    protected $mockContentObject;
+
+    /**
+     * @var FrontendConfigurationManager|MockObject|AccessibleObjectInterface
+     */
+    protected $frontendConfigurationManager;
+
+    /**
+     * @var TypoScriptService|MockObject|AccessibleObjectInterface
+     */
+    protected $mockTypoScriptService;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $GLOBALS['TSFE'] = new \stdClass();
+        $GLOBALS['TSFE']->tmpl = new \stdClass();
+        $this->mockContentObject = $this->getMockBuilder(ContentObjectRenderer::class)
+            ->setMethods(['getTreeList'])
+            ->getMock();
+        $this->frontendConfigurationManager = $this->getAccessibleMock(
+            FrontendConfigurationManager::class,
+            ['dummy'],
+            [],
+            '',
+            false
+        );
+        $this->frontendConfigurationManager->_set('contentObject', $this->mockContentObject);
+        $this->mockTypoScriptService = $this->getAccessibleMock(TypoScriptService::class);
+        $this->frontendConfigurationManager->_set('typoScriptService', $this->mockTypoScriptService);
+    }
+
+    /**
+     * @test
+     */
+    public function overrideControllerConfigurationWithSwitchableControllerActionsFromFlexFormMergesNonCacheableActions(): void
+    {
+        $frameworkConfiguration = [
+            'pluginName' => 'Pi1',
+            'extensionName' => 'SomeExtension',
+            'controllerConfiguration' => [
+                'MyExtension\\Controller\\Controller1' => [
+                    'alias' => 'Controller1',
+                    'actions' => ['action1 , action2']
+                ],
+                'MyExtension\\Controller\\Controller2' => [
+                    'alias' => 'Controller2',
+                    'actions' => ['action2', 'action1', 'action3'],
+                    'nonCacheableActions' => ['action2', 'action3']
+                ]
+            ]
+        ];
+        $flexFormConfiguration = [
+            'switchableControllerActions' => 'Controller1  -> action2;\\MyExtension\\Controller\\Controller2->action3;  Controller2->action1'
+        ];
+        $expectedResult = [
+            'pluginName' => 'Pi1',
+            'extensionName' => 'SomeExtension',
+            'controllerConfiguration' => [
+                'MyExtension\\Controller\\Controller1' => [
+                    'className' => 'MyExtension\\Controller\\Controller1',
+                    'alias' => 'Controller1',
+                    'actions' => ['action2']
+                ],
+                'MyExtension\\Controller\\Controller2' => [
+                    'className' => 'MyExtension\\Controller\\Controller2',
+                    'alias' => 'Controller2',
+                    'actions' => ['action3', 'action1'],
+                    'nonCacheableActions' => [1 => 'action3']
+                ]
+            ]
+        ];
+        $actualResult = $this->frontendConfigurationManager->_call(
+            'overrideControllerConfigurationWithSwitchableControllerActionsFromFlexForm',
+            $frameworkConfiguration,
+            $flexFormConfiguration
+        );
+        self::assertEquals($expectedResult, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function overrideControllerConfigurationWithSwitchableControllerActionsFromFlexFormReturnsUnchangedFrameworkConfigurationIfNoFlexFormConfigurationIsFound(
+    ): void {
+        $frameworkConfiguration = [
+            'pluginName' => 'Pi1',
+            'extensionName' => 'SomeExtension',
+            'controllerConfiguration' => [
+                'Controller1' => [
+                    'controller' => 'Controller1',
+                    'actions' => 'action1 , action2'
+                ],
+                'Controller2' => [
+                    'controller' => 'Controller2',
+                    'actions' => 'action2 , action1,action3',
+                    'nonCacheableActions' => 'action2, action3'
+                ]
+            ]
+        ];
+        $flexFormConfiguration = [];
+        $actualResult = $this->frontendConfigurationManager->_call(
+            'overrideControllerConfigurationWithSwitchableControllerActionsFromFlexForm',
+            $frameworkConfiguration,
+            $flexFormConfiguration
+        );
+        self::assertSame($frameworkConfiguration, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function overrideControllerConfigurationWithSwitchableControllerActionsThrowsExceptionIfFlexFormConfigurationIsInvalid(): void
+    {
+        $this->expectException(ParseErrorException::class);
+        $this->expectExceptionCode(1257146403);
+        $frameworkConfiguration = [
+            'pluginName' => 'Pi1',
+            'extensionName' => 'SomeExtension',
+            'controllerConfiguration' => [
+                'Controller1' => [
+                    'actions' => ['action1 , action2']
+                ],
+                'Controller2' => [
+                    'actions' => ['action2', 'action1', 'action3'],
+                    'nonCacheableActions' => ['action2', 'action3']
+                ]
+            ]
+        ];
+        $flexFormConfiguration = [
+            'switchableControllerActions' => 'Controller1->;Controller2->action3;Controller2->action1'
+        ];
+        $this->frontendConfigurationManager->_call(
+            'overrideControllerConfigurationWithSwitchableControllerActionsFromFlexForm',
+            $frameworkConfiguration,
+            $flexFormConfiguration
+        );
+    }
+}