From 4e498f4ea3730cb3747f71b259df8b9549a3f6ad Mon Sep 17 00:00:00 2001 From: Benjamin Franzke <bfr@qbus.de> Date: Mon, 20 Apr 2020 19:01:28 +0200 Subject: [PATCH] [TASK] Allow DI based services in localconf during extension install We fix PackageManager and ContainerBuilder to allow multiple container instances (including multiple cache identifiers) to be live. ExtensionManager will now load a new symfony container after installing a new extension, and before reloading all ext_localconf files. We need to ensure that possible services in ext_localconf can be loaded when they depend on symfony DI. Note: This is not a BUGFIX as this was never supported for extensions. But it may lead to hard-to-debug bugs when extension developers add a service to ext localconf but do not test the re-installation procedure before publishing the extension to TER. Releases: master Resolves: #91150 Change-Id: I9b01feae6fe2f1637ca653403336cd7d216483bd Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/64263 Reviewed-by: Simon Gilli <typo3@gilbertsoft.org> Reviewed-by: Raphael Zschorsch <rafu1987@gmail.com> Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de> Tested-by: TYPO3com <noreply@typo3.com> Tested-by: Raphael Zschorsch <rafu1987@gmail.com> Tested-by: Simon Gilli <typo3@gilbertsoft.org> Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de> --- .../DependencyInjection/ContainerBuilder.php | 13 +++++----- .../core/Classes/Package/PackageManager.php | 2 ++ .../Classes/Utility/InstallUtility.php | 24 ++++++++++++++++++- .../Tests/Unit/Utility/InstallUtilityTest.php | 12 +++++++++- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php b/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php index 944b523ec3d4..278c6f763ed6 100644 --- a/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php +++ b/typo3/sysext/core/Classes/DependencyInjection/ContainerBuilder.php @@ -35,9 +35,9 @@ use TYPO3\CMS\Core\Package\PackageManager; class ContainerBuilder { /** - * @var string + * @var array */ - protected $cacheIdentifier; + protected $cacheIdentifiers; /** * @var array @@ -163,15 +163,16 @@ class ContainerBuilder */ protected function getCacheIdentifier(PackageManager $packageManager): string { - return $this->cacheIdentifier ?? $this->createCacheIdentifier($packageManager->getCacheIdentifier()); + $packageManagerCacheIdentifier = $packageManager->getCacheIdentifier() ?? ''; + return $this->cacheIdentifiers[$packageManagerCacheIdentifier] ?? $this->createCacheIdentifier($packageManagerCacheIdentifier); } /** - * @param string|null $additionalIdentifier + * @param string $additionalIdentifier * @return string */ - protected function createCacheIdentifier(string $additionalIdentifier = null): string + protected function createCacheIdentifier(string $additionalIdentifier): string { - return $this->cacheIdentifier = 'DependencyInjectionContainer_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath() . ($additionalIdentifier ?? '') . 'DependencyInjectionContainer'); + return $this->cacheIdentifiers[$additionalIdentifier] = '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 e63846301c25..6934849f1c9c 100644 --- a/typo3/sysext/core/Classes/Package/PackageManager.php +++ b/typo3/sysext/core/Classes/Package/PackageManager.php @@ -761,6 +761,8 @@ class PackageManager implements SingletonInterface } $packageStatesCode = "<?php\n$fileDescription\nreturn " . ArrayUtility::arrayExport($this->packageStatesConfiguration) . ";\n"; GeneralUtility::writeFile($this->packageStatesPathAndFilename, $packageStatesCode, true); + // Cache identifier depends on package states file, therefore we invalidate the identifier + $this->cacheIdentifier = null; GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($this->packageStatesPathAndFilename); } diff --git a/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php b/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php index 8609def641bf..af072b9e1dc9 100644 --- a/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php +++ b/typo3/sysext/extensionmanager/Classes/Utility/InstallUtility.php @@ -44,6 +44,7 @@ use TYPO3\CMS\Extensionmanager\Event\AfterExtensionStaticDatabaseContentHasBeenI use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException; use TYPO3\CMS\Impexp\Import; use TYPO3\CMS\Impexp\Utility\ImportExportUtility; +use TYPO3\CMS\Install\Service\LateBootService; /** * Extension Manager Install Utility @@ -93,6 +94,11 @@ class InstallUtility implements SingletonInterface, LoggerAwareInterface */ protected $eventDispatcher; + /** + * @var LateBootService + */ + protected $lateBootService; + public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher) { $this->eventDispatcher = $eventDispatcher; @@ -154,6 +160,14 @@ class InstallUtility implements SingletonInterface, LoggerAwareInterface $this->registry = $registry; } + /** + * @param LateBootService $lateBootService + */ + public function injectLateBootService(LateBootService $lateBootService) + { + $this->lateBootService = $lateBootService; + } + /** * Helper function to install an extension * also processes db updates and clears the cache if the extension asks for it @@ -178,13 +192,21 @@ class InstallUtility implements SingletonInterface, LoggerAwareInterface } else { $this->cacheManager->flushCachesInGroup('system'); } + + // Load a new container as reloadCaches will load ext_localconf + $container = $this->lateBootService->getContainer(); + $backup = $this->lateBootService->makeCurrent($container); + $this->reloadCaches(); $this->updateDatabase(); foreach ($extensionKeys as $extensionKey) { $this->processExtensionSetup($extensionKey); - $this->eventDispatcher->dispatch(new AfterPackageActivationEvent($extensionKey, 'typo3-cms-extension', $this)); + $container->get(EventDispatcherInterface::class)->dispatch(new AfterPackageActivationEvent($extensionKey, 'typo3-cms-extension', $this)); } + + // Reset to the original container instance + $this->lateBootService->makeCurrent(null, $backup); } /** diff --git a/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php b/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php index 4325ea654e96..ce3ace2bec1c 100644 --- a/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php +++ b/typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php @@ -18,6 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Extensionmanager\Tests\Unit\Utility; use Prophecy\Argument; +use Psr\Container\ContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Yaml\Yaml; use TYPO3\CMS\Core\Cache\CacheManager; @@ -29,6 +30,7 @@ use TYPO3\CMS\Core\Utility\StringUtility; use TYPO3\CMS\Extensionmanager\Utility\DependencyUtility; use TYPO3\CMS\Extensionmanager\Utility\InstallUtility; use TYPO3\CMS\Extensionmanager\Utility\ListUtility; +use TYPO3\CMS\Install\Service\LateBootService; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; /** @@ -85,7 +87,15 @@ class InstallUtilityTest extends UnitTestCase 'importInitialFiles', ] ); - $this->installMock->injectEventDispatcher($this->prophesize(EventDispatcherInterface::class)->reveal()); + $eventDispatcherProphecy = $this->prophesize(EventDispatcherInterface::class); + $this->installMock->injectEventDispatcher($eventDispatcherProphecy->reveal()); + $this->installMock->injectLateBootService($this->prophesize(LateBootService::class)->reveal()); + $containerProphecy = $this->prophesize(ContainerInterface::class); + $containerProphecy->get(EventDispatcherInterface::class)->willReturn($eventDispatcherProphecy->reveal()); + $lateBootServiceProphecy = $this->prophesize(LateBootService::class); + $lateBootServiceProphecy->getContainer()->willReturn($containerProphecy->reveal()); + $lateBootServiceProphecy->makeCurrent(Argument::cetera())->willReturn([]); + $this->installMock->injectLateBootService($lateBootServiceProphecy->reveal()); $dependencyUtility = $this->getMockBuilder(DependencyUtility::class)->getMock(); $this->installMock->_set('dependencyUtility', $dependencyUtility); $this->installMock->expects(self::any()) -- GitLab