From ab0666b2c42ae7259a3bbf9eafc9e7068380386e Mon Sep 17 00:00:00 2001
From: Christian Kuhn <lolli@schwarzbu.ch>
Date: Wed, 6 Jun 2018 01:12:07 +0200
Subject: [PATCH] [TASK] Add require() to php capable cache backend

To allow loading php cache files more than once it is
necessary to add a require() method next to requireOnce()
to cache backends. This can be used if cache entries do
not execute one-time-per-process-only code, if they for
instance do not declare classes. This is needed to execute
for instance the functional test suite multiple times in
one process and to still make use of caching.
The cache_core php cache is affected by this: Those calls
are changed to require() instead of requireOnce() to make
them multi-loadable per request.
Note require() is not yet added to the PhpCapableBackendInterface
since that would be breaking, but marked as todo for v10.0

Resolves: #85648
Releases: master
Change-Id: Id711044e9554587dba38f976bd1c5bab23826de2
Reviewed-on: https://review.typo3.org/57686
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Tested-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
---
 .../Classes/Cache/Backend/FileBackend.php     | 23 +++++
 .../Classes/Cache/Backend/NullBackend.php     | 10 +++
 .../Backend/PhpCapableBackendInterface.php    |  2 +
 .../Cache/Backend/SimpleFileBackend.php       | 17 ++++
 .../Classes/Cache/Frontend/PhpFrontend.php    | 15 ++++
 .../Classes/Http/MiddlewareStackResolver.php  |  2 +-
 .../core/Classes/Package/PackageManager.php   |  2 +-
 .../Utility/ExtensionManagementUtility.php    |  6 +-
 .../Unit/Cache/Backend/FileBackendTest.php    | 87 +++++++++++++++++++
 .../Unit/Cache/Frontend/PhpFrontendTest.php   | 12 +++
 .../ExtensionManagementUtilityTest.php        | 32 +++----
 .../Controller/ConfigurationController.php    | 12 +--
 12 files changed, 189 insertions(+), 31 deletions(-)

diff --git a/typo3/sysext/core/Classes/Cache/Backend/FileBackend.php b/typo3/sysext/core/Classes/Cache/Backend/FileBackend.php
index 3adc246fa94d..c70cd961ed50 100644
--- a/typo3/sysext/core/Classes/Cache/Backend/FileBackend.php
+++ b/typo3/sysext/core/Classes/Cache/Backend/FileBackend.php
@@ -402,4 +402,27 @@ class FileBackend extends SimpleFileBackend implements FreezableBackendInterface
         $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
         return $this->isCacheFileExpired($pathAndFilename) ? false : require_once $pathAndFilename;
     }
+
+    /**
+     * Loads PHP code from the cache and require it right away.
+     *
+     * @param string $entryIdentifier An identifier which describes the cache entry to load
+     * @throws \InvalidArgumentException
+     * @return mixed Potential return value from the include operation
+     * @api
+     */
+    public function require(string $entryIdentifier)
+    {
+        if ($this->frozen) {
+            if (isset($this->cacheEntryIdentifiers[$entryIdentifier])) {
+                return require $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
+            }
+            return false;
+        }
+        if ($entryIdentifier !== PathUtility::basename($entryIdentifier)) {
+            throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1532528246);
+        }
+        $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
+        return $this->isCacheFileExpired($pathAndFilename) ? false : require $pathAndFilename;
+    }
 }
diff --git a/typo3/sysext/core/Classes/Cache/Backend/NullBackend.php b/typo3/sysext/core/Classes/Cache/Backend/NullBackend.php
index 62cc4c62a3e4..8acc59694076 100644
--- a/typo3/sysext/core/Classes/Cache/Backend/NullBackend.php
+++ b/typo3/sysext/core/Classes/Cache/Backend/NullBackend.php
@@ -128,4 +128,14 @@ class NullBackend extends AbstractBackend implements PhpCapableBackendInterface,
     public function requireOnce($identifier)
     {
     }
+
+    /**
+     * Does nothing
+     *
+     * @param string $identifier An identifier which describes the cache entry to load
+     * @api
+     */
+    public function require(string $identifier)
+    {
+    }
 }
diff --git a/typo3/sysext/core/Classes/Cache/Backend/PhpCapableBackendInterface.php b/typo3/sysext/core/Classes/Cache/Backend/PhpCapableBackendInterface.php
index b71cc2bedf94..c207c1b4e680 100644
--- a/typo3/sysext/core/Classes/Cache/Backend/PhpCapableBackendInterface.php
+++ b/typo3/sysext/core/Classes/Cache/Backend/PhpCapableBackendInterface.php
@@ -30,4 +30,6 @@ interface PhpCapableBackendInterface extends BackendInterface
      * @api
      */
     public function requireOnce($entryIdentifier);
+
+    // @todo: Add require() as breaking patch in v10.0 to the interface
 }
diff --git a/typo3/sysext/core/Classes/Cache/Backend/SimpleFileBackend.php b/typo3/sysext/core/Classes/Cache/Backend/SimpleFileBackend.php
index 3c7359122fcc..ea4ff390984e 100644
--- a/typo3/sysext/core/Classes/Cache/Backend/SimpleFileBackend.php
+++ b/typo3/sysext/core/Classes/Cache/Backend/SimpleFileBackend.php
@@ -362,4 +362,21 @@ class SimpleFileBackend extends AbstractBackend implements PhpCapableBackendInte
         }
         return file_exists($pathAndFilename) ? require_once $pathAndFilename : false;
     }
+
+    /**
+     * Loads PHP code from the cache and require it right away.
+     *
+     * @param string $entryIdentifier An identifier which describes the cache entry to load
+     * @return mixed Potential return value from the include operation
+     * @throws \InvalidArgumentException
+     * @api
+     */
+    public function require(string $entryIdentifier)
+    {
+        $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
+        if ($entryIdentifier !== PathUtility::basename($entryIdentifier)) {
+            throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1532528267);
+        }
+        return file_exists($pathAndFilename) ? require $pathAndFilename : false;
+    }
 }
diff --git a/typo3/sysext/core/Classes/Cache/Frontend/PhpFrontend.php b/typo3/sysext/core/Classes/Cache/Frontend/PhpFrontend.php
index 9baebfd54aba..ee5b8958cc01 100644
--- a/typo3/sysext/core/Classes/Cache/Frontend/PhpFrontend.php
+++ b/typo3/sysext/core/Classes/Cache/Frontend/PhpFrontend.php
@@ -113,4 +113,19 @@ class PhpFrontend extends AbstractFrontend
     {
         return $this->backend->requireOnce($entryIdentifier);
     }
+
+    /**
+     * Loads PHP code from the cache and require() it right away. Note require()
+     * in comparison to requireOnce() is only "safe" if the cache entry only contain stuff
+     * that can be required multiple times during one request. For instance a class definition
+     * would fail here.
+     *
+     * @param string $entryIdentifier An identifier which describes the cache entry to load
+     * @return mixed Potential return value from the include operation
+     * @api
+     */
+    public function require(string $entryIdentifier)
+    {
+        return $this->backend->require($entryIdentifier);
+    }
 }
diff --git a/typo3/sysext/core/Classes/Http/MiddlewareStackResolver.php b/typo3/sysext/core/Classes/Http/MiddlewareStackResolver.php
index 8d33e4121199..0163d73d0670 100644
--- a/typo3/sysext/core/Classes/Http/MiddlewareStackResolver.php
+++ b/typo3/sysext/core/Classes/Http/MiddlewareStackResolver.php
@@ -64,7 +64,7 @@ class MiddlewareStackResolver
         // Check if the registered middlewares from all active packages have already been cached
         $cacheIdentifier = $this->getCacheIdentifier($stackName);
         if ($this->cache->has($cacheIdentifier)) {
-            return $this->cache->requireOnce($cacheIdentifier);
+            return $this->cache->require($cacheIdentifier);
         }
 
         $allMiddlewares = $this->loadConfiguration();
diff --git a/typo3/sysext/core/Classes/Package/PackageManager.php b/typo3/sysext/core/Classes/Package/PackageManager.php
index c632c63b8506..9df6c56928ad 100644
--- a/typo3/sysext/core/Classes/Package/PackageManager.php
+++ b/typo3/sysext/core/Classes/Package/PackageManager.php
@@ -207,7 +207,7 @@ class PackageManager implements SingletonInterface
     protected function loadPackageManagerStatesFromCache()
     {
         $cacheEntryIdentifier = $this->getCacheEntryIdentifier();
-        if ($cacheEntryIdentifier === null || !$this->coreCache->has($cacheEntryIdentifier) || !($packageCache = $this->coreCache->requireOnce($cacheEntryIdentifier))) {
+        if ($cacheEntryIdentifier === null || !$this->coreCache->has($cacheEntryIdentifier) || !($packageCache = $this->coreCache->require($cacheEntryIdentifier))) {
             throw new Exception\PackageManagerCacheUnavailableException('The package state cache could not be loaded.', 1393883342);
         }
         $this->packageStatesConfiguration = $packageCache['packageStatesConfiguration'];
diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
index e2894e8e57c0..c6af44fb453d 100644
--- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
+++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
@@ -1534,7 +1534,7 @@ tt_content.' . $key . $suffix . ' {
             /** @var $codeCache \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface */
             $codeCache = self::getCacheManager()->getCache('cache_core');
             if ($codeCache->has($cacheIdentifier)) {
-                $codeCache->requireOnce($cacheIdentifier);
+                $codeCache->require($cacheIdentifier);
             } else {
                 self::loadSingleExtLocalconfFiles();
                 self::createExtLocalconfCacheEntry();
@@ -1632,7 +1632,7 @@ tt_content.' . $key . $suffix . ' {
             $cacheIdentifier = static::getBaseTcaCacheIdentifier();
             /** @var $codeCache \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface */
             $codeCache = static::getCacheManager()->getCache('cache_core');
-            $cacheData = $codeCache->requireOnce($cacheIdentifier);
+            $cacheData = $codeCache->require($cacheIdentifier);
             if ($cacheData) {
                 $GLOBALS['TCA'] = $cacheData['tca'];
                 GeneralUtility::setSingletonInstance(
@@ -1778,7 +1778,7 @@ tt_content.' . $key . $suffix . ' {
             /** @var $codeCache \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface */
             $codeCache = self::getCacheManager()->getCache('cache_core');
             if ($codeCache->has($cacheIdentifier)) {
-                $codeCache->requireOnce($cacheIdentifier);
+                $codeCache->require($cacheIdentifier);
             } else {
                 self::loadSingleExtTablesFiles();
                 self::createExtTablesCacheEntry();
diff --git a/typo3/sysext/core/Tests/Unit/Cache/Backend/FileBackendTest.php b/typo3/sysext/core/Tests/Unit/Cache/Backend/FileBackendTest.php
index 21382e89bb75..55c706e0e3ee 100644
--- a/typo3/sysext/core/Tests/Unit/Cache/Backend/FileBackendTest.php
+++ b/typo3/sysext/core/Tests/Unit/Cache/Backend/FileBackendTest.php
@@ -694,6 +694,93 @@ class FileBackendTest extends UnitTestCase
         $this->assertEquals('foo', $loadedData);
     }
 
+    /**
+     * @test
+     * @dataProvider invalidEntryIdentifiers
+     * @param string $identifier
+     */
+    public function requireThrowsExceptionForInvalidIdentifier(string $identifier): void
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1532528246);
+        $mockCache = $this->createMock(AbstractFrontend::class);
+        $mockCache->expects($this->atLeastOnce())->method('getIdentifier')->will($this->returnValue('UnitTestCache'));
+
+        $backend = $this->getMockBuilder(FileBackend::class)
+            ->setMethods(['dummy'])
+            ->disableOriginalConstructor()
+            ->getMock();
+        $backend->setCacheDirectory('vfs://Foo/');
+        $backend->setCache($mockCache);
+
+        $backend->require($identifier);
+    }
+
+    /**
+     * @test
+     */
+    public function requireIncludesAndReturnsResultOfIncludedPhpFile(): void
+    {
+        $mockCache = $this->createMock(AbstractFrontend::class);
+        $mockCache->expects($this->atLeastOnce())->method('getIdentifier')->will($this->returnValue('UnitTestCache'));
+        $backend = $this->getMockBuilder(FileBackend::class)
+            ->setMethods(['dummy'])
+            ->disableOriginalConstructor()
+            ->getMock();
+        $backend->setCacheDirectory('vfs://Foo/');
+        $backend->setCache($mockCache);
+
+        $entryIdentifier = 'SomePhpEntry2';
+
+        $data = '<?php return "foo2"; ?>';
+        $backend->set($entryIdentifier, $data);
+
+        $loadedData = $backend->require($entryIdentifier);
+        $this->assertEquals('foo2', $loadedData);
+    }
+
+    /**
+     * @test
+     */
+    public function requireDoesNotCheckExpiryTimeIfBackendIsFrozen(): void
+    {
+        $mockCache = $this->createMock(AbstractFrontend::class);
+        $mockCache->expects($this->atLeastOnce())->method('getIdentifier')->will($this->returnValue('UnitTestCache'));
+        $backend = $this->getMockBuilder(FileBackend::class)
+            ->setMethods(['isCacheFileExpired'])
+            ->disableOriginalConstructor()
+            ->getMock();
+        $backend->setCacheDirectory('vfs://Foo/');
+        $backend->setCache($mockCache);
+
+        $backend->expects($this->once())->method('isCacheFileExpired'); // Indirectly called by freeze() -> get()
+
+        $data = '<?php return "foo"; ?>';
+        $backend->set('FooEntry2', $data);
+
+        $backend->freeze();
+
+        $loadedData = $backend->require('FooEntry2');
+        $this->assertEquals('foo', $loadedData);
+    }
+
+    /**
+     * @test
+     */
+    public function requireCanLoadSameEntryMultipleTimes(): void
+    {
+        $frontendProphecy = $this->prophesize(AbstractFrontend::class);
+        $frontendProphecy->getIdentifier()->willReturn('UnitTestCache');
+        $subject = new FileBackend('Testing');
+        $subject->setCacheDirectory('vfs://Foo/');
+        $subject->setCache($frontendProphecy->reveal());
+        $subject->set('BarEntry', '<?php return "foo"; ?>');
+        $loadedData = $subject->require('BarEntry');
+        $this->assertEquals('foo', $loadedData);
+        $loadedData = $subject->require('BarEntry');
+        $this->assertEquals('foo', $loadedData);
+    }
+
     /**
      * @test
      * @throws Exception
diff --git a/typo3/sysext/core/Tests/Unit/Cache/Frontend/PhpFrontendTest.php b/typo3/sysext/core/Tests/Unit/Cache/Frontend/PhpFrontendTest.php
index d1e7f961d4b4..3f2618e03557 100644
--- a/typo3/sysext/core/Tests/Unit/Cache/Frontend/PhpFrontendTest.php
+++ b/typo3/sysext/core/Tests/Unit/Cache/Frontend/PhpFrontendTest.php
@@ -75,4 +75,16 @@ class PhpFrontendTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         $result = $cache->requireOnce('Foo-Bar');
         $this->assertSame('hello world!', $result);
     }
+
+    /**
+     * @test
+     */
+    public function requireCallsTheBackendsRequireMethod()
+    {
+        $mockBackend = $this->createMock(\TYPO3\CMS\Core\Cache\Backend\SimpleFileBackend::class);
+        $mockBackend->expects($this->once())->method('require')->with('Foo-Bar')->will($this->returnValue('hello world!'));
+        $cache = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend::class, 'PhpFrontend', $mockBackend);
+        $result = $cache->require('Foo-Bar');
+        $this->assertSame('hello world!', $result);
+    }
 }
diff --git a/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
index 54d2296c1f8e..6e1f83cfde1f 100644
--- a/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
+++ b/typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
@@ -1216,7 +1216,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
     public function loadExtLocalconfRequiresCacheFileIfExistsAndCachingIsAllowed()
     {
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1227,7 +1227,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
         $mockCacheManager->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
         ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
         $mockCache->expects($this->any())->method('has')->will($this->returnValue(true));
-        $mockCache->expects($this->once())->method('requireOnce');
+        $mockCache->expects($this->once())->method('require');
         ExtensionManagementUtility::loadExtLocalconf(true);
     }
 
@@ -1340,7 +1340,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
         file_put_contents($extLocalconfLocation, "<?php\n\n" . $uniqueStringInLocalconf . "\n\n?>");
         $GLOBALS['TYPO3_LOADED_EXT'] = new LoadedExtensionsArray($packageManager);
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1363,7 +1363,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
         $packageManager = $this->createMockPackageManagerWithMockPackage($extensionName);
         $GLOBALS['TYPO3_LOADED_EXT'] = new LoadedExtensionsArray($packageManager);
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1385,7 +1385,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
     public function createExtLocalconfCacheEntryWritesCacheEntryWithNoTags()
     {
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1439,7 +1439,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
     public function loadBaseTcaRequiresCacheFileIfExistsAndCachingIsAllowed()
     {
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1449,7 +1449,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->getMock();
         $mockCacheManager->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
         ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
-        $mockCache->expects($this->once())->method('requireOnce')->willReturn(['tca' => [], 'categoryRegistry' => \serialize(CategoryRegistry::getInstance())]);
+        $mockCache->expects($this->once())->method('require')->willReturn(['tca' => [], 'categoryRegistry' => \serialize(CategoryRegistry::getInstance())]);
         ExtensionManagementUtilityAccessibleProxy::loadBaseTca(true);
     }
 
@@ -1471,7 +1471,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
         $tableConfiguration = '<?php return array(\'foo\' => \'' . $uniqueStringInTableConfiguration . '\'); ?>';
         file_put_contents($packagePath . 'Configuration/TCA/' . $uniqueTableName . '.php', $tableConfiguration);
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1481,7 +1481,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->getMock();
         $mockCacheManager->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
         ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
-        $mockCache->expects($this->once())->method('requireOnce')->will($this->returnValue(false));
+        $mockCache->expects($this->once())->method('require')->will($this->returnValue(false));
         $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains($uniqueStringInTableConfiguration), $this->anything());
         ExtensionManagementUtility::loadBaseTca(true);
     }
@@ -1492,7 +1492,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
     public function loadBaseTcaWritesCacheEntryWithNoTags()
     {
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1502,7 +1502,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->getMock();
         $mockCacheManager->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
         ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
-        $mockCache->expects($this->once())->method('requireOnce')->will($this->returnValue(false));
+        $mockCache->expects($this->once())->method('require')->will($this->returnValue(false));
         $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->anything(), $this->equalTo([]));
         ExtensionManagementUtilityAccessibleProxy::loadBaseTca();
     }
@@ -1547,7 +1547,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
     public function loadExtTablesRequiresCacheFileIfExistsAndCachingIsAllowed()
     {
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1558,7 +1558,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
         $mockCacheManager->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
         ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
         $mockCache->expects($this->any())->method('has')->will($this->returnValue(true));
-        $mockCache->expects($this->once())->method('requireOnce');
+        $mockCache->expects($this->once())->method('require');
         // Reset the internal cache access tracking variable of extMgm
         // This method is only in the ProxyClass!
         ExtensionManagementUtilityAccessibleProxy::resetExtTablesWasReadFromCacheOnceBoolean();
@@ -1584,7 +1584,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ]
         ];
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1608,7 +1608,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             $extensionName => [],
         ];
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
@@ -1630,7 +1630,7 @@ class ExtensionManagementUtilityTest extends UnitTestCase
     public function createExtTablesCacheEntryWritesCacheEntryWithNoTags()
     {
         $mockCache = $this->getMockBuilder(PhpFrontend::class)
-            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'requireOnce'])
+            ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
 
diff --git a/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php b/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php
index 50c9e3240899..e6b647be61b7 100644
--- a/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php
+++ b/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php
@@ -21,8 +21,7 @@ use TYPO3\CMS\Backend\Configuration\SiteTcaConfiguration;
 use TYPO3\CMS\Backend\Routing\Router;
 use TYPO3\CMS\Backend\Template\ModuleTemplate;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Cache\Backend\NullBackend;
-use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
+use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\MiddlewareStackResolver;
 use TYPO3\CMS\Core\Localization\LanguageService;
@@ -225,18 +224,11 @@ class ConfigurationController
         } elseif ($selectedTreeDetails['type'] === 'httpMiddlewareStacks') {
             // Keep the order of the keys
             $sortKeysByName = false;
-            // Fake a PHP frontend with a null backend to avoid PHP Opcache conflicts
-            // When using >requireOnce() multiple times in one request
-            $cache = GeneralUtility::makeInstance(
-                PhpFrontend::class,
-                'middleware',
-                GeneralUtility::makeInstance(NullBackend::class, 'Production')
-            );
             $stackResolver = GeneralUtility::makeInstance(
                 MiddlewareStackResolver::class,
                 GeneralUtility::makeInstance(PackageManager::class),
                 GeneralUtility::makeInstance(DependencyOrderingService::class),
-                $cache
+                GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core')
             );
             $renderArray = [];
             foreach (['frontend', 'backend'] as $stackName) {
-- 
GitLab