From 727fb0e53f902c83bd0d55fcedbd36388f5715f8 Mon Sep 17 00:00:00 2001 From: Christian Kuhn <lolli@schwarzbu.ch> Date: Mon, 28 Aug 2023 17:51:14 +0200 Subject: [PATCH] [BUGFIX] Avoid static state in LocalizationUtility The extbase ConfigurationManager is (unfortunately) a stateful singleton that we can not get rid of without a bigger rewrite. While stateful singletons are bad enough, the extbase LocalizationUtility makes this worse by parking an instance of ConfigurationManager in a static property, re-using it as a "cached" singleton. LocalizationUtility does this since it in itself is static, which makes this service just so convenient to use. When it comes to sub requests and similar, static state is doomed and we need to get rid of it, we've had a couple of patches in v12 dealing with similar things. Mid-term, extbase LocalizationUtility needs to vanish anyways, but in the meantime, we have to get rid of static state that kills sub request scope. The patch removes the static $configurationManager property and adapts functional tests that already showed the current solution was a hack. There are various upper and lower cache layers that ensure removing this "cache layer" won't make things more expensive in practice, which allows us to remove this static state without further fallback. In main, this needs a TF update: > composer u typo3/testing-framework In 12.4, this need a TF raise: > composer req --dev typo3/testing-framework:^8.0.3 Resolves: #101779 Releases: main, 12.4 Change-Id: Ie5db07b0475f612a996d369ab3417672b33fbb2d Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/80737 Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch> Tested-by: core-ci <typo3@b13.com> Tested-by: Christian Kuhn <lolli@schwarzbu.ch> --- composer.json | 2 +- composer.lock | 14 ++--- .../Classes/Utility/LocalizationUtility.php | 19 +------ .../Utility/LocalizationUtilityTest.php | 53 +++++++------------ .../Utility/LocalizationUtilityTest.php | 29 +++------- 5 files changed, 34 insertions(+), 83 deletions(-) diff --git a/composer.json b/composer.json index 1d35e0910027..1e772ed36119 100644 --- a/composer.json +++ b/composer.json @@ -118,7 +118,7 @@ "sokil/php-isocodes-db-i18n": "^4.0.13", "symfony/translation": "^6.2", "typo3/cms-styleguide": "^12.0.2", - "typo3/testing-framework": "^8.0.2", + "typo3/testing-framework": "^8.0.3", "webmozart/assert": "^1.11.0" }, "suggest": { diff --git a/composer.lock b/composer.lock index 6509d1dc34b0..7f235b310498 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8080136c7d55f58568c412c63b32ba78", + "content-hash": "ac3ccb1566155180ebadeb00f1d5ae34", "packages": [ { "name": "bacon/bacon-qr-code", @@ -8825,16 +8825,16 @@ }, { "name": "typo3/testing-framework", - "version": "8.0.2", + "version": "8.0.3", "source": { "type": "git", "url": "https://github.com/TYPO3/testing-framework.git", - "reference": "0f64df79ad145ee193f692a2e84e7d6c993d7b5f" + "reference": "82d8a12ccf186b919c080a10276ed30590cfb7f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/0f64df79ad145ee193f692a2e84e7d6c993d7b5f", - "reference": "0f64df79ad145ee193f692a2e84e7d6c993d7b5f", + "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/82d8a12ccf186b919c080a10276ed30590cfb7f3", + "reference": "82d8a12ccf186b919c080a10276ed30590cfb7f3", "shasum": "" }, "require": { @@ -8893,9 +8893,9 @@ "support": { "general": "https://typo3.org/support/", "issues": "https://github.com/TYPO3/testing-framework/issues", - "source": "https://github.com/TYPO3/testing-framework/tree/8.0.2" + "source": "https://github.com/TYPO3/testing-framework/tree/8.0.3" }, - "time": "2023-08-02T16:10:26+00:00" + "time": "2023-08-28T17:20:23+00:00" } ], "aliases": [], diff --git a/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php b/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php index 5ac791568d99..438f71cdb587 100644 --- a/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php +++ b/typo3/sysext/extbase/Classes/Utility/LocalizationUtility.php @@ -36,11 +36,6 @@ class LocalizationUtility { protected static string $locallangPath = 'Resources/Private/Language/'; - /** - * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface - */ - protected static $configurationManager; - /** * Returns the localized label of the LOCAL_LANG key, $key. * @@ -177,7 +172,7 @@ class LocalizationUtility */ protected static function loadTypoScriptLabels(string $extensionName): array { - $configurationManager = static::getConfigurationManager(); + $configurationManager = GeneralUtility::makeInstance(ConfigurationManagerInterface::class); $frameworkConfiguration = $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, $extensionName); if (!is_array($frameworkConfiguration['_LOCAL_LANG'] ?? false)) { return []; @@ -233,18 +228,6 @@ class LocalizationUtility return $result; } - /** - * Returns instance of the configuration manager - */ - protected static function getConfigurationManager(): ConfigurationManagerInterface - { - if (static::$configurationManager !== null) { - return static::$configurationManager; - } - static::$configurationManager = GeneralUtility::makeInstance(ConfigurationManagerInterface::class); - return static::$configurationManager; - } - /** * Returns the currently configured "site language" if a site is configured (= resolved) * in the current request. diff --git a/typo3/sysext/extbase/Tests/Functional/Utility/LocalizationUtilityTest.php b/typo3/sysext/extbase/Tests/Functional/Utility/LocalizationUtilityTest.php index 493c078d21d1..70cebf0ce641 100644 --- a/typo3/sysext/extbase/Tests/Functional/Utility/LocalizationUtilityTest.php +++ b/typo3/sysext/extbase/Tests/Functional/Utility/LocalizationUtilityTest.php @@ -17,38 +17,16 @@ declare(strict_types=1); namespace TYPO3\CMS\Extbase\Tests\Functional\Utility; -use PHPUnit\Framework\MockObject\MockObject; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; final class LocalizationUtilityTest extends FunctionalTestCase { - protected ConfigurationManagerInterface&MockObject $configurationManagerInterfaceMock; - protected array $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/label_test']; - protected function setUp(): void - { - parent::setUp(); - $reflectionClass = new \ReflectionClass(LocalizationUtility::class); - $this->configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); - $property = $reflectionClass->getProperty('configurationManager'); - $property->setValue(null, $this->configurationManagerInterfaceMock); - } - - /** - * Reset static properties - */ - protected function tearDown(): void - { - $reflectionClass = new \ReflectionClass(LocalizationUtility::class); - $property = $reflectionClass->getProperty('configurationManager'); - $property->setValue(null, null); - parent::tearDown(); - } - /** * @test */ @@ -122,18 +100,15 @@ final class LocalizationUtilityTest extends FunctionalTestCase * @dataProvider translateDataProvider * @test */ - public function translateTestWithBackendUserLanguage( - string $key, - string $languageKey, - string $expected, - array $altLanguageKeys = [], - array $arguments = null - ): void { + public function translateTestWithBackendUserLanguage(string $key, string $languageKey, string $expected, array $altLanguageKeys = [], array $arguments = null): void + { // No TypoScript overrides - $this->configurationManagerInterfaceMock + $configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); + $configurationManagerInterfaceMock ->method('getConfiguration') ->with('Framework', 'label_test', null) ->willReturn([]); + GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManagerInterfaceMock); $GLOBALS['BE_USER'] = new BackendUserAuthentication(); $GLOBALS['BE_USER']->user = ['lang' => $languageKey]; @@ -152,10 +127,12 @@ final class LocalizationUtilityTest extends FunctionalTestCase array $arguments = null ): void { // No TypoScript overrides - $this->configurationManagerInterfaceMock + $configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); + $configurationManagerInterfaceMock ->method('getConfiguration') ->with('Framework', 'label_test', null) ->willReturn([]); + GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManagerInterfaceMock); self::assertSame($expected, LocalizationUtility::translate($key, 'label_test', $arguments, $languageKey, $altLanguageKeys)); } @@ -168,7 +145,8 @@ final class LocalizationUtilityTest extends FunctionalTestCase public function loadTypoScriptLabels(): void { $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK; - $this->configurationManagerInterfaceMock + $configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); + $configurationManagerInterfaceMock ->expects(self::atLeastOnce()) ->method('getConfiguration') ->with($configurationType, 'label_test', null) @@ -188,6 +166,7 @@ final class LocalizationUtilityTest extends FunctionalTestCase ], ], ]); + GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManagerInterfaceMock); self::assertSame('key1 value from TS core', LocalizationUtility::translate('key1', 'label_test', languageKey: 'da')); // Label from XLF file, no override @@ -203,7 +182,8 @@ final class LocalizationUtilityTest extends FunctionalTestCase public function clearLabelWithTypoScript(): void { $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK; - $this->configurationManagerInterfaceMock + $configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); + $configurationManagerInterfaceMock ->expects(self::atLeastOnce()) ->method('getConfiguration') ->with($configurationType, 'label_test', null) @@ -214,6 +194,7 @@ final class LocalizationUtilityTest extends FunctionalTestCase ], ], ]); + GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManagerInterfaceMock); $result = LocalizationUtility::translate('key1', 'label_test', languageKey: 'da'); @@ -244,11 +225,13 @@ final class LocalizationUtilityTest extends FunctionalTestCase ]; $configurationType = ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK; - $this->configurationManagerInterfaceMock + $configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); + $configurationManagerInterfaceMock ->expects(self::atLeastOnce()) ->method('getConfiguration') ->with($configurationType, 'core', null) ->willReturn($typoScriptLocalLang); + GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManagerInterfaceMock); $result = LocalizationUtility::translate('key1', 'core', languageKey: 'da'); diff --git a/typo3/sysext/extbase/Tests/FunctionalDeprecated/Utility/LocalizationUtilityTest.php b/typo3/sysext/extbase/Tests/FunctionalDeprecated/Utility/LocalizationUtilityTest.php index dfde867cdd82..a4765ffd5791 100644 --- a/typo3/sysext/extbase/Tests/FunctionalDeprecated/Utility/LocalizationUtilityTest.php +++ b/typo3/sysext/extbase/Tests/FunctionalDeprecated/Utility/LocalizationUtilityTest.php @@ -19,6 +19,7 @@ namespace TYPO3\CMS\Extbase\Tests\FunctionalDeprecated\Utility; use PHPUnit\Framework\MockObject\MockObject; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; @@ -29,26 +30,6 @@ final class LocalizationUtilityTest extends FunctionalTestCase protected array $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/label_test']; - protected function setUp(): void - { - parent::setUp(); - $reflectionClass = new \ReflectionClass(LocalizationUtility::class); - $this->configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); - $property = $reflectionClass->getProperty('configurationManager'); - $property->setValue($this->configurationManagerInterfaceMock); - } - - /** - * Reset static properties - */ - protected function tearDown(): void - { - $reflectionClass = new \ReflectionClass(LocalizationUtility::class); - $property = $reflectionClass->getProperty('configurationManager'); - $property->setValue(null); - parent::tearDown(); - } - public static function translateDataProvider(): array { return [ @@ -75,10 +56,12 @@ final class LocalizationUtilityTest extends FunctionalTestCase array $arguments = null ): void { // No TypoScript overrides - $this->configurationManagerInterfaceMock + $configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); + $configurationManagerInterfaceMock ->method('getConfiguration') ->with('Framework', 'label_test', null) ->willReturn([]); + GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManagerInterfaceMock); $GLOBALS['BE_USER'] = new BackendUserAuthentication(); $GLOBALS['BE_USER']->user = ['lang' => $languageKey]; @@ -97,10 +80,12 @@ final class LocalizationUtilityTest extends FunctionalTestCase array $arguments = null ): void { // No TypoScript overrides - $this->configurationManagerInterfaceMock + $configurationManagerInterfaceMock = $this->createMock(ConfigurationManagerInterface::class); + $configurationManagerInterfaceMock ->method('getConfiguration') ->with('Framework', 'label_test', null) ->willReturn([]); + GeneralUtility::setSingletonInstance(ConfigurationManagerInterface::class, $configurationManagerInterfaceMock); self::assertSame($expected, LocalizationUtility::translate($key, 'label_test', $arguments, $languageKey, $altLanguageKeys)); } -- GitLab