diff --git a/typo3/sysext/core/Classes/Core/Bootstrap.php b/typo3/sysext/core/Classes/Core/Bootstrap.php index 655ee4fb4b4bfc86c15e8db0a9031e593d4d6c0e..a2f13f9393d5e3037566a0470284df09543697c1 100644 --- a/typo3/sysext/core/Classes/Core/Bootstrap.php +++ b/typo3/sysext/core/Classes/Core/Bootstrap.php @@ -26,8 +26,10 @@ use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Exception\InvalidBackendException; use TYPO3\CMS\Core\Cache\Exception\InvalidCacheException; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; use TYPO3\CMS\Core\Cache\Frontend\VariableFrontend; use TYPO3\CMS\Core\Configuration\ConfigurationManager; +use TYPO3\CMS\Core\DependencyInjection\Cache\ContainerBackend; use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder; use TYPO3\CMS\Core\Imaging\IconRegistry; use TYPO3\CMS\Core\IO\PharStreamWrapperInterceptor; @@ -111,6 +113,7 @@ class Bootstrap static::setMemoryLimit(); $assetsCache = static::createCache('assets', $disableCaching); + $dependencyInjectionContainerCache = static::createCache('di'); $bootState = new \stdClass; $bootState->done = false; @@ -121,6 +124,7 @@ class Bootstrap ApplicationContext::class => Environment::getContext(), ConfigurationManager::class => $configurationManager, LogManager::class => $logManager, + 'cache.di' => $dependencyInjectionContainerCache, 'cache.core' => $coreCache, 'cache.assets' => $assetsCache, PackageManager::class => $packageManager, @@ -129,7 +133,7 @@ class Bootstrap 'boot.state' => $bootState, ]); - $container = $builder->createDependencyInjectionContainer($packageManager, $coreCache, $failsafe); + $container = $builder->createDependencyInjectionContainer($packageManager, $dependencyInjectionContainerCache, $failsafe); // Push the container to GeneralUtility as we want to make sure its // makeInstance() method creates classes using the container from now on. @@ -310,7 +314,11 @@ class Bootstrap */ public static function createCache(string $identifier, bool $disableCaching = false): FrontendInterface { - $configuration = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'][$identifier] ?? []; + $cacheConfigurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] ?? []; + $cacheConfigurations['di']['frontend'] = PhpFrontend::class; + $cacheConfigurations['di']['backend'] = ContainerBackend::class; + $cacheConfigurations['di']['options'] = []; + $configuration = $cacheConfigurations[$identifier] ?? []; $frontend = $configuration['frontend'] ?? VariableFrontend::class; $backend = $configuration['backend'] ?? Typo3DatabaseBackend::class; diff --git a/typo3/sysext/core/Classes/DependencyInjection/Cache/ContainerBackend.php b/typo3/sysext/core/Classes/DependencyInjection/Cache/ContainerBackend.php new file mode 100644 index 0000000000000000000000000000000000000000..5e7176a98091dfde72d00120b443ff44fdd0f71c --- /dev/null +++ b/typo3/sysext/core/Classes/DependencyInjection/Cache/ContainerBackend.php @@ -0,0 +1,36 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Core\DependencyInjection\Cache; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Core\Cache\Backend\SimpleFileBackend; + +/** + * @internal + */ +class ContainerBackend extends SimpleFileBackend +{ + public function flush() + { + // disable cache flushing + } + + public function set($entryIdentifier, $data, array $tags = [], $lifetime = null) + { + // Remove stale cache files, once a new DI container was built + parent::flush(); + parent::set($entryIdentifier, $data, $tags, $lifetime); + } +} diff --git a/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php b/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php index 9a60f4ade4cade9b4d138cd7a40bd970c3671858..2cabda533ab2e91fa92ca7501a466a386888fc15 100644 --- a/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php +++ b/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Information\Typo3Version; use TYPO3\CMS\Core\Package\PackageManager; @@ -62,6 +63,9 @@ class ContainerBuilder */ public function createDependencyInjectionContainer(PackageManager $packageManager, FrontendInterface $cache, bool $failsafe = false): ContainerInterface { + if (!$cache instanceof PhpFrontend) { + throw new \RuntimeException('Cache must be instance of PhpFrontend', 1582022226); + } $serviceProviderRegistry = new ServiceProviderRegistry($packageManager, $failsafe); if ($failsafe) { @@ -70,29 +74,16 @@ class ContainerBuilder $container = null; - $cacheIdentifier = $this->getCacheIdentifier(); + $cacheIdentifier = $this->getCacheIdentifier($packageManager); $containerClassName = $cacheIdentifier; $hasCache = $cache->requireOnce($cacheIdentifier) !== false; if (!$hasCache) { $containerBuilder = $this->buildContainer($packageManager, $serviceProviderRegistry); - $code = $this->dumpContainer($containerBuilder, $cache); - - // In theory we could use the $containerBuilder directly as $container, - // but as we patch the compiled source to use - // GeneralUtility::makeInstanceForDi, we need to use the compiled container. - // Once we remove support for singletons configured in ext_localconf.php - // and $GLOBALS['TYPO_CONF_VARS']['SYS']['Objects'], we can remove this, - // and use `$container = $containerBuilder` directly - $hasCache = $cache->requireOnce($cacheIdentifier) !== false; - if (!$hasCache) { - // $cacheIdentifier may be unavailable if the 'core' cache iis configured to - // use the NullBackend - eval($code); - } + $this->dumpContainer($containerBuilder, $cache, $cacheIdentifier); + $cache->requireOnce($cacheIdentifier); } - $fullyQualifiedContainerClassName = '\\' . $containerClassName; - $container = new $fullyQualifiedContainerClassName(); + $container = new $containerClassName(); foreach ($this->defaultServices as $id => $service) { $container->set('_early.' . $id, $service); @@ -129,7 +120,7 @@ class ContainerBuilder // Store defaults entries in the DIC container // We need to use a workaround using aliases for synthetic services // But that's common in symfony (same technique is used to provide the - // symfony container interface as well. + // Symfony container interface as well). foreach (array_keys($this->defaultServices) as $id) { $syntheticId = '_early.' . $id; $containerBuilder->register($syntheticId)->setSynthetic(true)->setPublic(true); @@ -144,11 +135,11 @@ class ContainerBuilder /** * @param SymfonyContainerBuilder $containerBuilder * @param FrontendInterface $cache + * @param string $cacheIdentifier * @return string */ - protected function dumpContainer(SymfonyContainerBuilder $containerBuilder, FrontendInterface $cache): string + protected function dumpContainer(SymfonyContainerBuilder $containerBuilder, FrontendInterface $cache, string $cacheIdentifier): string { - $cacheIdentifier = $this->getCacheIdentifier(); $containerClassName = $cacheIdentifier; $phpDumper = new PhpDumper($containerBuilder); @@ -165,18 +156,20 @@ class ContainerBuilder } /** + * @param PackageManager $packageManager * @return string */ - protected function getCacheIdentifier(): string + protected function getCacheIdentifier(PackageManager $packageManager): string { - return $this->cacheIdentifier ?? $this->createCacheIdentifier(); + return $this->cacheIdentifier ?? $this->createCacheIdentifier($packageManager->getCacheIdentifier()); } /** + * @param string|null $additionalIdentifier * @return string */ - protected function createCacheIdentifier(): string + protected function createCacheIdentifier(string $additionalIdentifier = null): string { - return $this->cacheIdentifier = 'DependencyInjectionContainer_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . 'DependencyInjectionContainer'); + return $this->cacheIdentifier = 'DependencyInjectionContainer_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . ($additionalIdentifier ?? '') . 'DependencyInjectionContainer'); } } diff --git a/typo3/sysext/core/Classes/Package/PackageManager.php b/typo3/sysext/core/Classes/Package/PackageManager.php index 46c5b48e3990d74abc1a644057715fe5cbfb86fa..041767402b5ce6a4b1d7185c839bcadab1f6a5f3 100644 --- a/typo3/sysext/core/Classes/Package/PackageManager.php +++ b/typo3/sysext/core/Classes/Package/PackageManager.php @@ -139,9 +139,10 @@ class PackageManager implements SingletonInterface } /** - * @return string + * @internal + * @return string | null */ - protected function getCacheIdentifier() + public function getCacheIdentifier() { if ($this->cacheIdentifier === null) { $mTime = @filemtime($this->packageStatesPathAndFilename); diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php index 959b20feaa44dfbf1e5a37ffa8ea2a75471ed560..92878d73d17c6a05c33cf1886430bbd96ea59e58 100644 --- a/typo3/sysext/core/Classes/ServiceProvider.php +++ b/typo3/sysext/core/Classes/ServiceProvider.php @@ -65,10 +65,12 @@ class ServiceProvider extends AbstractServiceProvider $defaultCaches = [ $container->get('cache.core'), $container->get('cache.assets'), + $container->get('cache.di'), ]; $cacheManager = self::new($container, Cache\CacheManager::class, [$disableCaching]); $cacheManager->setCacheConfigurations($cacheConfigurations); + $cacheConfigurations['di']['groups'] = ['system']; foreach ($defaultCaches as $cache) { $cacheManager->registerCache($cache, $cacheConfigurations[$cache->getIdentifier()]['groups'] ?? ['all']); } diff --git a/typo3/sysext/install/Classes/Service/ClearCacheService.php b/typo3/sysext/install/Classes/Service/ClearCacheService.php index 175c11c96d9804f1811640ed1c2d185825a4053d..b7c5257a98094a9a69d4d64909ac9a2b78a202f2 100644 --- a/typo3/sysext/install/Classes/Service/ClearCacheService.php +++ b/typo3/sysext/install/Classes/Service/ClearCacheService.php @@ -14,7 +14,6 @@ namespace TYPO3\CMS\Install\Service; * The TYPO3 project - inspiring people to share! */ -use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -24,6 +23,10 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; */ class ClearCacheService { + private const legacyDatabaseCacheTables = [ + 'cache_treelist', + ]; + /** * @var LateBootService */ @@ -49,41 +52,47 @@ class ClearCacheService */ public function clearAll() { - // Delete typo3temp/Cache - GeneralUtility::flushDirectory(Environment::getVarPath() . '/cache', true, true); - - // Get all table names from Default connection starting with 'cf_' and truncate them + // Low level flush of legacy database cache tables $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); - $connection = $connectionPool->getConnectionByName('Default'); - $tableNames = $connection->getSchemaManager()->listTableNames(); - foreach ($tableNames as $tableName) { - if (strpos($tableName, 'cf_') === 0 || $tableName === 'cache_treelist') { - $connection->truncate($tableName); - } + foreach (self::legacyDatabaseCacheTables as $tableName) { + $connection = $connectionPool->getConnectionForTable($tableName); + $connection->truncate($tableName); } - // check tables on other connections - $remappedTables = isset($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']) - ? array_keys((array)$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']) - : []; - foreach ($remappedTables as $tableName) { - if (strpos((string)$tableName, 'cf_') === 0 || $tableName === 'cache_treelist') { - $connectionPool->getConnectionForTable($tableName)->truncate($tableName); - } - } + // Flush all caches defined in TYPO3_CONF_VARS, but not the ones defined by extensions in ext_localconf.php + $baseCaches = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] ?? []; + $this->flushCaches($baseCaches); + + // Remove DI container cache (this might be removed in preference of functionality to rebuild this cache) + // We need to remove using the remove method because the DI cache backend disables the flush method + $container = $this->lateBootService->getContainer(); + $container->get('cache.di')->remove(get_class($container)); // From this point on, the code may fatal, if some broken extension is loaded. $this->lateBootService->loadExtLocalconfDatabaseAndExtTables(); - // The cache manager is already instantiated in the install tool - // (both in the failsafe and the late boot container), but - // with some hacked settings to disable caching of extbase and fluid. - // We want a "fresh" object here to operate on a different cache setup. - // cacheManager implements SingletonInterface, so the only way to get a "fresh" - // instance is by circumventing makeInstance and/or the objectManager and - // using new directly! + $extensionCaches = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] ?? []; + // Loose comparison on purpose to allow changed ordering of the array + if ($baseCaches != $extensionCaches) { + // When configuration has changed during loading of extensions (due to ext_localconf.php), flush all caches again + $this->flushCaches($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']); + } + } + + /** + * The cache manager is already instantiated in the install tool + * (both in the failsafe and the late boot container), but + * with settings to disable caching (all caches using NullBackend). + * We want a "fresh" object here to operate with the really configured cache backends. + * CacheManager implements SingletonInterface, so the only way to get a "fresh" + * instance is by circumventing makeInstance and using new directly! + * + * @param array $cacheConfiguration + */ + private function flushCaches(array $cacheConfiguration): void + { $cacheManager = new \TYPO3\CMS\Core\Cache\CacheManager(); - $cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']); + $cacheManager->setCacheConfigurations($cacheConfiguration); $cacheManager->flushCaches(); } } diff --git a/typo3/sysext/install/Classes/Service/LateBootService.php b/typo3/sysext/install/Classes/Service/LateBootService.php index 291d486eafac4dfddbd79bbb2a7133885e112bfb..d3486017a03aca9b572e1381276db497c12d87ef 100644 --- a/typo3/sysext/install/Classes/Service/LateBootService.php +++ b/typo3/sysext/install/Classes/Service/LateBootService.php @@ -69,15 +69,12 @@ class LateBootService private function prepareContainer(): ContainerInterface { $packageManager = $this->failsafeContainer->get(PackageManager::class); - - // Use caching for the full boot – uncached symfony autowiring for every install-tool lateboot request would be too slow. - $disableCaching = false; - $coreCache = Bootstrap::createCache('core', $disableCaching); + $dependencyInjectionContainerCache = $this->failsafeContainer->get('cache.di'); $failsafe = false; // Build a non-failsafe container which is required for loading ext_localconf - return $this->container = $this->containerBuilder->createDependencyInjectionContainer($packageManager, $coreCache, $failsafe); + return $this->container = $this->containerBuilder->createDependencyInjectionContainer($packageManager, $dependencyInjectionContainerCache, $failsafe); } /**