[TASK] Do not create caches during ext_localconf.php phase 71/58371/7
authorBenjamin Franzke <bfr@qbus.de>
Fri, 7 Sep 2018 09:26:43 +0000 (11:26 +0200)
committerFrank Naegler <frank.naegler@typo3.org>
Fri, 28 Sep 2018 10:31:44 +0000 (12:31 +0200)
CacheManager has a design problem:
The CacheManager is used to create the core_cache. That core_cache
is used to read the (possibly) cached CacheManager configuration,
which is then used to configure the already-being-used CacheManager.

That means the initialization sequence currently is like:

new CacheManager(!$failsafe) | setInitialCacheConfiguration() |
    loadExtLocalconf() | setFinalCachingFrameworkConfiguration()

Between initial creation of the CacheManager and
setFinalCachingFrameworkConfiguration() the CacheManager is in a limbo
state, as it may already be used to create a cache although the final
configuration (which may be configured in ext_localconf.php) for that
cache has not been set. The final configuration (for that created cache)
will then be ignored, as the cache has already been created.

This is not a theoretical problem, but is actually happening in core
for two caches (introduced due to patches in the v9 development
phase, more on those later).

In v10 we want to change the sequence to the following:

$coreCache = createCoreCache() | loadExtLocalconf |
    new CacheManager(!$failsafe, $cachingConfiguration, $coreCache);

We want to delay CacheManager until ext_localconf.php has been loaded
(maybe also after baseTca loading) in v10.
Therefore GeneralUtility::makeInstance(CacheManager::class) usage in
ext_localconf.php is deprecated now.

Note: The core cache cannot be modified in ext_localconf.php - that was
always the case and wouldn't make sense (as that cache is used to actually
load the cached ext_localconf.php)

Two caches are actually loaded too early during ext_localconf.php loading
currently. We fix these as a drive-by:
 * extbase_reflection: the extbase Object\Container is instanciated in
   EXT:extbase/ext_localconf.php. The Object\Container then instanciates
   the ReflectionService in its constructor which itself creates the
   extbase_reflection using the CacheManager (all of that during
   ext_localconf.php loading).
   This is actually a regression introduced in
   https://review.typo3.org/54381
   We change the Container to load the reflection cache on demand.
 * assets:
   * IconRegistry uses the 'assets' cache and loads cached backend
     icons during object construction.
     This cache was introduced in https://review.typo3.org/c/54020/
     using  the core cache, and was later changed to 'assets' in
     https://review.typo3.org/54061
   * PageRenderer loads cached requireJS configuration from 'assets'

   We inject assetsCache to these services from Bootstrap for now. We'll
   only be able to properly refactor this, when dropping support for
   ext_localconf.php altogether.

We also adapt the ExtensionManagementUtility to retrieve the coreCache
as parameter, instead of statically. Although these methods are marked
@internal, we keep them for 9LTS to not break extensions (using this
regardless of being @internal, e.g. helhum/typo3-console) shortly before
the release.

Change-Id: I22935dae3acb6e8de14fa98a6b88f3477a3ea313
Releases: master
Resolves: #86353
Resolves: #86371
Resolves: #86372
Reviewed-on: https://review.typo3.org/58371
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
typo3/sysext/core/Classes/Cache/CacheManager.php
typo3/sysext/core/Classes/Core/Bootstrap.php
typo3/sysext/core/Classes/Imaging/IconRegistry.php
typo3/sysext/core/Classes/Page/PageRenderer.php
typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-86353-DeprecateCacheManagerUsageInExt_localconfphp.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Utility/AccessibleProxies/ExtensionManagementUtilityAccessibleProxy.php
typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
typo3/sysext/extbase/Classes/Object/Container/Container.php

index 7dc233a..947356e 100644 (file)
@@ -69,6 +69,16 @@ class CacheManager implements SingletonInterface
     protected $disableCaching = false;
 
     /**
+     * Used by Bootstrap to define whether the configuration has been set finally.
+     * Controls whether a deprecation warning is logged in getCache().
+     * This property will be removed in v10.
+     *
+     * @var bool
+     * @internal
+     */
+    protected $limbo = false;
+
+    /**
      * @param bool $disableCaching
      */
     public function __construct(bool $disableCaching = false)
@@ -282,6 +292,10 @@ class CacheManager implements SingletonInterface
      */
     protected function createCache($identifier)
     {
+        // @deprecated will be removed with TYPO3 v10
+        if ($this->limbo) {
+            trigger_error('Usage of ' . self::class . '->createCache(\'' . $identifier . '\') in ext_localconf.php is deprecated and will not be supported in TYPO3 v10.0', E_USER_DEPRECATED);
+        }
         if (isset($this->cacheConfigurations[$identifier]['frontend'])) {
             $frontend = $this->cacheConfigurations[$identifier]['frontend'];
         } else {
@@ -338,4 +352,18 @@ class CacheManager implements SingletonInterface
 
         $this->registerCache($frontendInstance);
     }
+
+    /**
+     * Sets the limbo state
+     *
+     * If limbo is enable, then getCache() will log a deprecation warning.
+     * This method will be removed in v10.
+     *
+     * @param bool $limbo
+     * @internal
+     */
+    public function setLimbo(bool $limbo)
+    {
+        $this->limbo = $limbo;
+    }
 }
index 3084074..d189df4 100644 (file)
@@ -22,11 +22,13 @@ use Psr\Container\NotFoundExceptionInterface;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Imaging\IconRegistry;
 use TYPO3\CMS\Core\IO\PharStreamWrapperInterceptor;
 use TYPO3\CMS\Core\Localization\Locales;
 use TYPO3\CMS\Core\Log\LogManager;
 use TYPO3\CMS\Core\Package\FailsafePackageManager;
 use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\PharStreamWrapper\Behavior;
@@ -57,6 +59,11 @@ class Bootstrap
     protected $earlyInstances = [];
 
     /**
+     * @var bool
+     */
+    protected $limbo = false;
+
+    /**
      * Disable direct creation of this object.
      */
     protected function __construct()
@@ -94,9 +101,12 @@ class Bootstrap
 
         $logManager = new LogManager($requestId);
         $cacheManager = static::createCacheManager($failsafe ? true : false);
+        $coreCache = $cacheManager->getCache('cache_core');
+        $assetsCache = $cacheManager->getCache('assets');
+        $cacheManager->setLimbo(true);
         $packageManager = static::createPackageManager(
             $failsafe ? FailsafePackageManager::class : PackageManager::class,
-            $cacheManager->getCache('cache_core')
+            $coreCache
         );
 
         // Push singleton instances to GeneralUtility and ExtensionManagementUtility
@@ -123,12 +133,15 @@ class Bootstrap
         ];
 
         if (!$failsafe) {
-            static::loadTypo3LoadedExtAndExtLocalconf(true);
+            IconRegistry::setCache($assetsCache);
+            PageRenderer::setCache($assetsCache);
+            static::loadTypo3LoadedExtAndExtLocalconf(true, $coreCache);
             static::setFinalCachingFrameworkCacheConfiguration($cacheManager);
             static::unsetReservedGlobalVariables();
-            static::loadBaseTca();
+            static::loadBaseTca(true, $coreCache);
             static::checkEncryptionKey();
         }
+        $cacheManager->setLimbo(false);
 
         $defaultContainerEntries = [
             ClassLoader::class => $classLoader,
@@ -531,12 +544,16 @@ class Bootstrap
      * Load ext_localconf of extensions
      *
      * @param bool $allowCaching
+     * @param FrontendInterface $coreCache
      * @return Bootstrap|null
      * @internal This is not a public API method, do not use in own extensions
      */
-    public static function loadTypo3LoadedExtAndExtLocalconf($allowCaching = true)
+    public static function loadTypo3LoadedExtAndExtLocalconf($allowCaching = true, FrontendInterface $coreCache = null)
     {
-        ExtensionManagementUtility::loadExtLocalconf($allowCaching);
+        if ($allowCaching) {
+            $coreCache = $coreCache ?? GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core');
+        }
+        ExtensionManagementUtility::loadExtLocalconf($allowCaching, $coreCache);
         return static::$instance;
     }
 
@@ -805,12 +822,16 @@ class Bootstrap
      * This will mainly set up $TCA through extMgm API.
      *
      * @param bool $allowCaching True, if loading TCA from cache is allowed
+     * @param FrontendInterface $coreCache
      * @return Bootstrap|null
      * @internal This is not a public API method, do not use in own extensions
      */
-    public static function loadBaseTca(bool $allowCaching = true)
+    public static function loadBaseTca(bool $allowCaching = true, FrontendInterface $coreCache = null)
     {
-        ExtensionManagementUtility::loadBaseTca($allowCaching);
+        if ($allowCaching) {
+            $coreCache = $coreCache ?? GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core');
+        }
+        ExtensionManagementUtility::loadBaseTca($allowCaching, $coreCache);
         return static::$instance;
     }
 
index 36dc239..4db8154 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Core\Imaging;
 
 use Symfony\Component\Finder\Finder;
 use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Exception;
 use TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider;
@@ -516,6 +517,11 @@ class IconRegistry implements SingletonInterface
     protected $defaultIconIdentifier = 'default-not-found';
 
     /**
+     * @var FrontendInterface
+     */
+    protected static $cache = null;
+
+    /**
      * The constructor
      */
     public function __construct()
@@ -524,6 +530,15 @@ class IconRegistry implements SingletonInterface
     }
 
     /**
+     * @param FrontendInterface $coreCache
+     * @internal
+     */
+    public static function setCache(FrontendInterface $cache)
+    {
+        static::$cache = $cache;
+    }
+
+    /**
      * Initialize the registry
      * This method can be called multiple times, depending on initialization status.
      * In some cases e.g. TCA is not available, the method must be called multiple times.
@@ -557,7 +572,7 @@ class IconRegistry implements SingletonInterface
     {
         $cacheIdentifier = 'BackendIcons_' . sha1(TYPO3_version . Environment::getProjectPath() . 'BackendIcons');
         /** @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $assetsCache */
-        $assetsCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('assets');
+        $assetsCache = static::$cache ?? GeneralUtility::makeInstance(CacheManager::class)->getCache('assets');
         $cacheEntry = $assetsCache->get($cacheIdentifier);
 
         if ($cacheEntry !== false) {
index 4560431..0cb75eb 100644 (file)
@@ -382,6 +382,11 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface
     protected $metaTagRegistry;
 
     /**
+     * @var FrontendInterface
+     */
+    protected static $cache = null;
+
+    /**
      * @param string $templateFile Declare the used template file. Omit this parameter will use default template
      */
     public function __construct($templateFile = '')
@@ -405,6 +410,14 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface
     }
 
     /**
+     * @param FrontendInterface $cache
+     */
+    public static function setCache(FrontendInterface $cache)
+    {
+        static::$cache = $cache;
+    }
+
+    /**
      * Reset all vars to initial values
      */
     protected function reset()
@@ -1384,7 +1397,7 @@ class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface
         $isDevelopment = GeneralUtility::getApplicationContext()->isDevelopment();
         $cacheIdentifier = 'requireJS_' . md5(implode(',', $loadedExtensions) . ($isDevelopment ? ':dev' : '') . GeneralUtility::getIndpEnv('TYPO3_REQUEST_SCRIPT'));
         /** @var FrontendInterface $cache */
-        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('assets');
+        $cache = static::$cache ?? GeneralUtility::makeInstance(CacheManager::class)->getCache('assets');
         $this->requireJsConfig = $cache->get($cacheIdentifier);
 
         // if we did not get a configuration from the cache, compute and store it in the cache
index 336eebd..26ab16b 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Core\Utility;
 use Symfony\Component\Finder\Finder;
 use TYPO3\CMS\Backend\Routing\Route;
 use TYPO3\CMS\Backend\Routing\Router;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Category\CategoryRegistry;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Imaging\IconRegistry;
@@ -1528,19 +1529,19 @@ tt_content.' . $key . $suffix . ' {
      * extensions should not use it!
      *
      * @param bool $allowCaching Whether or not to load / create concatenated cache file
+     * @param FrontendInterface $codeCache
      * @access private
      */
-    public static function loadExtLocalconf($allowCaching = true)
+    public static function loadExtLocalconf($allowCaching = true, FrontendInterface $codeCache = null)
     {
         if ($allowCaching) {
+            $codeCache = $codeCache ?? self::getCacheManager()->getCache('cache_core');
             $cacheIdentifier = self::getExtLocalconfCacheIdentifier();
-            /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $codeCache */
-            $codeCache = self::getCacheManager()->getCache('cache_core');
             if ($codeCache->has($cacheIdentifier)) {
                 $codeCache->require($cacheIdentifier);
             } else {
                 self::loadSingleExtLocalconfFiles();
-                self::createExtLocalconfCacheEntry();
+                self::createExtLocalconfCacheEntry($codeCache);
             }
         } else {
             self::loadSingleExtLocalconfFiles();
@@ -1571,8 +1572,10 @@ tt_content.' . $key . $suffix . ' {
 
     /**
      * Create cache entry for concatenated ext_localconf.php files
+     *
+     * @param FrontendInterface $codeCache
      */
-    protected static function createExtLocalconfCacheEntry()
+    protected static function createExtLocalconfCacheEntry(FrontendInterface $codeCache)
     {
         $phpCodeToCache = [];
         // Set same globals as in loadSingleExtLocalconfFiles()
@@ -1606,7 +1609,7 @@ tt_content.' . $key . $suffix . ' {
         $phpCodeToCache = implode(LF, $phpCodeToCache);
         // Remove all start and ending php tags from content
         $phpCodeToCache = preg_replace('/<\\?php|\\?>/is', '', $phpCodeToCache);
-        self::getCacheManager()->getCache('cache_core')->set(self::getExtLocalconfCacheIdentifier(), $phpCodeToCache);
+        $codeCache->set(self::getExtLocalconfCacheIdentifier(), $phpCodeToCache);
     }
 
     /**
@@ -1632,12 +1635,11 @@ tt_content.' . $key . $suffix . ' {
      * @param bool $allowCaching Whether or not to load / create concatenated cache file
      * @access private
      */
-    public static function loadBaseTca($allowCaching = true)
+    public static function loadBaseTca($allowCaching = true, FrontendInterface $codeCache = null)
     {
         if ($allowCaching) {
+            $codeCache = $codeCache ?? self::getCacheManager()->getCache('cache_core');
             $cacheIdentifier = static::getBaseTcaCacheIdentifier();
-            /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $codeCache */
-            $codeCache = static::getCacheManager()->getCache('cache_core');
             $cacheData = $codeCache->require($cacheIdentifier);
             if ($cacheData) {
                 $GLOBALS['TCA'] = $cacheData['tca'];
@@ -1650,7 +1652,7 @@ tt_content.' . $key . $suffix . ' {
                 );
             } else {
                 static::buildBaseTcaFromSingleFiles();
-                static::createBaseTcaCacheFile();
+                static::createBaseTcaCacheFile($codeCache);
             }
         } else {
             static::buildBaseTcaFromSingleFiles();
@@ -1742,11 +1744,11 @@ tt_content.' . $key . $suffix . ' {
     /**
      * Cache base $GLOBALS['TCA'] to cache file to require the whole thing in one
      * file for next access instead of cycling through all extensions again.
+     *
+     * @param FrontendInterface $codeCache
      */
-    protected static function createBaseTcaCacheFile()
+    protected static function createBaseTcaCacheFile(FrontendInterface $codeCache)
     {
-        /** @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $codeCache */
-        $codeCache = self::getCacheManager()->getCache('cache_core');
         $codeCache->set(
             static::getBaseTcaCacheIdentifier(),
             'return '
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-86353-DeprecateCacheManagerUsageInExt_localconfphp.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-86353-DeprecateCacheManagerUsageInExt_localconfphp.rst
new file mode 100644 (file)
index 0000000..febe39e
--- /dev/null
@@ -0,0 +1,36 @@
+.. include:: ../../Includes.txt
+
+=======================================================================
+Deprecation: #86353 - Deprecate CacheManager usage in ext_localconf.php
+=======================================================================
+
+See :issue:`86353`
+
+Description
+===========
+
+:php:`\TYPO3\CMS\Core\Cache\CacheManager->getCache()` usage during
+:file:`ext_localconf.php` loading phase has been deprecated.
+
+
+Impact
+======
+
+Using :php:`\TYPO3\CMS\Core\Cache\CacheManager->getCache()` in
+:file:`ext_localconf.php` will log a deprecation warning.
+
+
+Affected Installations
+======================
+
+All installations with third party extensions that use
+:php:`\TYPO3\CMS\Core\Cache\CacheManager->getCache()` in
+:file:`ext_localconf.php`.
+
+
+Migration
+=========
+
+Load caches on demand, when actually needed.
+
+.. index:: PHP-API, NotScanned
index 30b3f03..926fe07 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Core\Tests\Unit\Utility\AccessibleProxies;
  */
 
 use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 
 /**
@@ -52,9 +53,9 @@ class ExtensionManagementUtilityAccessibleProxy extends ExtensionManagementUtili
         self::$extTablesWasReadFromCacheOnce = false;
     }
 
-    public static function createExtLocalconfCacheEntry()
+    public static function createExtLocalconfCacheEntry(FrontendInterface $cache)
     {
-        parent::createExtLocalconfCacheEntry();
+        parent::createExtLocalconfCacheEntry($cache);
     }
 
     public static function createExtTablesCacheEntry()
index 1b15bf3..1feef3a 100644 (file)
@@ -1338,14 +1338,8 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        /** @var CacheManager|\PHPUnit_Framework_MockObject_MockObject $mockCacheManager */
-        $mockCacheManager = $this->getMockBuilder(CacheManager::class)
-            ->setMethods(['getCache'])
-            ->getMock();
-        $mockCacheManager->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
-        ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
         $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->stringContains($uniqueStringInLocalconf), $this->anything());
-        ExtensionManagementUtilityAccessibleProxy::createExtLocalconfCacheEntry();
+        ExtensionManagementUtilityAccessibleProxy::createExtLocalconfCacheEntry($mockCache);
     }
 
     /**
@@ -1361,16 +1355,10 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        /** @var CacheManager|\PHPUnit_Framework_MockObject_MockObject $mockCacheManager */
-        $mockCacheManager = $this->getMockBuilder(CacheManager::class)
-            ->setMethods(['getCache'])
-            ->getMock();
-        $mockCacheManager->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
-        ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
         $mockCache->expects($this->once())
             ->method('set')
             ->with($this->anything(), $this->logicalNot($this->stringContains($extensionName)), $this->anything());
-        ExtensionManagementUtilityAccessibleProxy::createExtLocalconfCacheEntry();
+        ExtensionManagementUtilityAccessibleProxy::createExtLocalconfCacheEntry($mockCache);
     }
 
     /**
@@ -1382,17 +1370,10 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->setMethods(['getIdentifier', 'set', 'get', 'getByTag', 'has', 'remove', 'flush', 'flushByTag', 'require'])
             ->disableOriginalConstructor()
             ->getMock();
-
-        /** @var CacheManager|\PHPUnit_Framework_MockObject_MockObject $mockCacheManager */
-        $mockCacheManager = $this->getMockBuilder(CacheManager::class)
-            ->setMethods(['getCache'])
-            ->getMock();
-        $mockCacheManager->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
-        ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
         $mockCache->expects($this->once())->method('set')->with($this->anything(), $this->anything(), $this->equalTo([]));
         $packageManager = $this->createMockPackageManagerWithMockPackage($this->getUniqueId());
         ExtensionManagementUtility::setPackageManager($packageManager);
-        ExtensionManagementUtilityAccessibleProxy::createExtLocalconfCacheEntry();
+        ExtensionManagementUtilityAccessibleProxy::createExtLocalconfCacheEntry($mockCache);
     }
 
     /////////////////////////////////////////
@@ -1438,14 +1419,8 @@ class ExtensionManagementUtilityTest extends UnitTestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        /** @var CacheManager|\PHPUnit_Framework_MockObject_MockObject $mockCacheManager */
-        $mockCacheManager = $this->getMockBuilder(CacheManager::class)
-            ->setMethods(['getCache'])
-            ->getMock();
-        $mockCacheManager->expects($this->any())->method('getCache')->will($this->returnValue($mockCache));
-        ExtensionManagementUtilityAccessibleProxy::setCacheManager($mockCacheManager);
         $mockCache->expects($this->once())->method('require')->willReturn(['tca' => [], 'categoryRegistry' => \serialize(CategoryRegistry::getInstance())]);
-        ExtensionManagementUtilityAccessibleProxy::loadBaseTca(true);
+        ExtensionManagementUtilityAccessibleProxy::loadBaseTca(true, $mockCache);
     }
 
     /**
index 98750cf..d465417 100644 (file)
@@ -62,17 +62,6 @@ class Container implements \TYPO3\CMS\Core\SingletonInterface
     private $reflectionService;
 
     /**
-     * Constructor is protected since container should
-     * be a singleton.
-     *
-     * @see getContainer()
-     */
-    public function __construct()
-    {
-        $this->reflectionService = GeneralUtility::makeInstance(ReflectionService::class, GeneralUtility::makeInstance(CacheManager::class));
-    }
-
-    /**
      * Internal method to create the class instantiator, extracted to be mockable
      *
      * @return \Doctrine\Instantiator\InstantiatorInterface
@@ -109,7 +98,7 @@ class Container implements \TYPO3\CMS\Core\SingletonInterface
     public function getEmptyObject($className)
     {
         $className = $this->getImplementationClassName($className);
-        $classSchema = $this->reflectionService->getClassSchema($className);
+        $classSchema = $this->getReflectionService()->getClassSchema($className);
         $object = $this->getInstantiator()->instantiate($className);
         $this->injectDependencies($object, $classSchema);
         $this->initializeObject($object);
@@ -145,7 +134,7 @@ class Container implements \TYPO3\CMS\Core\SingletonInterface
             return $this->singletonInstances[$className];
         }
 
-        $classSchema = $this->reflectionService->getClassSchema($className);
+        $classSchema = $this->getReflectionService()->getClassSchema($className);
         $classIsSingleton = $classSchema->isSingleton();
         if (!$classIsSingleton) {
             if (array_key_exists($className, $this->prototypeObjectsWhichAreCurrentlyInstanciated) !== false) {
@@ -308,7 +297,7 @@ class Container implements \TYPO3\CMS\Core\SingletonInterface
      */
     public function isSingleton($className)
     {
-        return $this->reflectionService->getClassSchema($className)->isSingleton();
+        return $this->getReflectionService()->getClassSchema($className)->isSingleton();
     }
 
     /**
@@ -328,4 +317,17 @@ class Container implements \TYPO3\CMS\Core\SingletonInterface
     {
         return GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class);
     }
+
+    /**
+     * Lazy load ReflectionService.
+     *
+     * Required as this class is being loaded in ext_localconf.php and we MUST not
+     * create caches in ext_localconf.php (which ReflectionService needs to do).
+     *
+     * @return ReflectionService
+     */
+    protected function getReflectionService(): ReflectionService
+    {
+        return $this->reflectionService ?? ($this->reflectionService = GeneralUtility::makeInstance(ReflectionService::class, GeneralUtility::makeInstance(CacheManager::class)));
+    }
 }