Commit 53fa40b0 authored by Benni Mack's avatar Benni Mack Committed by Benjamin Franzke
Browse files

[TASK] Use runtime cache for LanguageService

This change moves state for caching purposes
from $GLOBALS[LANG] into the runtime cache
allowing for further reducing the need to keep
the LanguageService in a global state.

Resolves: #94415
Releases: master
Change-Id: Id8bc974fc65e630695d5f86a13ff0c137cd8f3ba
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/69551


Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
parent 4cce5462
......@@ -16,6 +16,7 @@
namespace TYPO3\CMS\Core\Localization;
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -55,13 +56,6 @@ class LanguageService
*/
public $debugKey = false;
/**
* Internal cache for ll-labels (filled as labels are requested)
*
* @var array
*/
protected $LL_labels_cache = [];
/**
* List of language dependencies for actual language. This is used for local variants of a language
* that depend on their "main" language, like Brazilian Portuguese or Canadian French.
......@@ -70,35 +64,23 @@ class LanguageService
*/
protected $languageDependencies = [];
/**
* An internal cache for storing loaded files, see readLLfile()
*
* @var array
*/
protected $languageFileCache = [];
/**
* @var string[][]
*/
protected $labels = [];
/**
* @var Locales
*/
protected $locales;
/**
* @var LocalizationFactory
*/
protected $localizationFactory;
protected Locales $locales;
protected LocalizationFactory $localizationFactory;
protected FrontendInterface $runtimeCache;
/**
* @internal use one of the factory methods instead
*/
public function __construct(Locales $locales, LocalizationFactory $localizationFactory)
public function __construct(Locales $locales, LocalizationFactory $localizationFactory, FrontendInterface $runtimeCache)
{
$this->locales = $locales;
$this->localizationFactory = $localizationFactory;
$this->runtimeCache = $runtimeCache;
$this->debugKey = (bool)$GLOBALS['TYPO3_CONF_VARS']['BE']['languageDebug'];
}
......@@ -184,9 +166,10 @@ class LanguageService
*/
public function sL($input)
{
$identifier = $input . '_' . (int)$this->debugKey;
if (isset($this->LL_labels_cache[$this->lang][$identifier])) {
return $this->LL_labels_cache[$this->lang][$identifier];
$cacheIdentifier = 'labels_' . md5($input . '_' . (int)$this->debugKey);
$cacheEntry = $this->runtimeCache->get($cacheIdentifier);
if ($cacheEntry !== false) {
return $cacheEntry;
}
if (strpos(trim($input), 'LLL:') === 0) {
$restStr = substr(trim($input), 4);
......@@ -204,7 +187,7 @@ class LanguageService
$output = $input;
}
$output .= $this->debugLL($input);
$this->LL_labels_cache[$this->lang][$identifier] = $output;
$this->runtimeCache->set($cacheIdentifier, $output);
return $output;
}
......@@ -323,8 +306,10 @@ class LanguageService
*/
protected function readLLfile($fileRef): array
{
if (isset($this->languageFileCache[$fileRef . $this->lang])) {
return $this->languageFileCache[$fileRef . $this->lang];
$cacheIdentifier = 'labels_file_' . md5($fileRef . $this->lang);
$cacheEntry = $this->runtimeCache->get($cacheIdentifier);
if (is_array($cacheEntry)) {
return $cacheEntry;
}
if ($this->lang !== 'default') {
......@@ -345,8 +330,8 @@ class LanguageService
ArrayUtility::mergeRecursiveWithOverrule($localLanguage[$this->lang], $tempLL[$language], true, false);
}
}
$this->languageFileCache[$fileRef . $this->lang] = $localLanguage;
$this->runtimeCache->set($cacheIdentifier, $localLanguage);
return $localLanguage;
}
......
......@@ -18,24 +18,23 @@ declare(strict_types=1);
namespace TYPO3\CMS\Core\Localization;
use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
class LanguageServiceFactory
{
/**
* @var Locales
*/
protected $locales;
/**
* @var LocalizationFactory
*/
protected $localizationFactory;
protected Locales $locales;
protected LocalizationFactory $localizationFactory;
protected FrontendInterface $runtimeCache;
public function __construct(Locales $locales, LocalizationFactory $localizationFactory)
{
public function __construct(
Locales $locales,
LocalizationFactory $localizationFactory,
FrontendInterface $runtimeCache
) {
$this->locales = $locales;
$this->localizationFactory = $localizationFactory;
$this->runtimeCache = $runtimeCache;
}
/**
......@@ -46,7 +45,7 @@ class LanguageServiceFactory
*/
public function create(string $locale): LanguageService
{
$obj = new LanguageService($this->locales, $this->localizationFactory);
$obj = new LanguageService($this->locales, $this->localizationFactory, $this->runtimeCache);
$obj->init($locale);
return $obj;
}
......
......@@ -241,7 +241,8 @@ class ServiceProvider extends AbstractServiceProvider
{
return self::new($container, Localization\LanguageServiceFactory::class, [
$container->get(Localization\Locales::class),
$container->get(Localization\LocalizationFactory::class)
$container->get(Localization\LocalizationFactory::class),
$container->get(Cache\CacheManager::class)->getCache('runtime')
]);
}
......
......@@ -140,6 +140,8 @@ services:
TYPO3\CMS\Core\Localization\LanguageService:
shared: false
public: true
arguments:
$runtimeCache: '@cache.runtime'
deprecated:
package: 'typo3/cms-core'
version: '11.3'
......
......@@ -352,7 +352,7 @@ class LocalizationUtilityTest extends UnitTestCase
$cacheManagerProphecy->getCache('l10n')->willReturn($cacheFrontendProphecy->reveal());
$cacheFrontendProphecy->get(Argument::cetera())->willReturn(false);
$cacheFrontendProphecy->set(Argument::cetera())->willReturn(null);
$GLOBALS['LANG'] = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()));
$GLOBALS['LANG'] = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()), $cacheFrontendProphecy->reveal());
self::assertEquals($expected, LocalizationUtility::translate($key, 'core', $arguments, $languageKey, $altLanguageKeys));
}
......@@ -517,7 +517,7 @@ class LocalizationUtilityTest extends UnitTestCase
$cacheManagerProphecy->getCache('l10n')->willReturn($cacheFrontendProphecy->reveal());
$cacheFrontendProphecy->get(Argument::cetera())->willReturn(false);
$cacheFrontendProphecy->set(Argument::cetera())->willReturn(null);
$GLOBALS['LANG'] = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()));
$GLOBALS['LANG'] = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()), $cacheFrontendProphecy->reveal());
$result = LocalizationUtility::translate('key1', 'core', null, 'dk');
self::assertNotNull($result);
......@@ -564,7 +564,7 @@ class LocalizationUtilityTest extends UnitTestCase
$cacheManagerProphecy->getCache('l10n')->willReturn($cacheFrontendProphecy->reveal());
$cacheFrontendProphecy->get(Argument::cetera())->willReturn(false);
$cacheFrontendProphecy->set(Argument::cetera())->willReturn(null);
$GLOBALS['LANG'] = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()));
$GLOBALS['LANG'] = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()), $cacheFrontendProphecy->reveal());
$result = LocalizationUtility::translate('key1', 'core', null, 'dk');
self::assertNotNull($result);
......
......@@ -100,7 +100,7 @@ class TranslationServiceTest extends UnitTestCase
'getLanguageService'
], [], '', false);
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()));
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()), $cacheFrontendProphecy->reveal());
$this->mockTranslationService
->expects(self::any())
->method('getLanguageService')
......
......@@ -80,7 +80,7 @@ class AbstractMenuContentObjectTest extends UnitTestCase
$cacheManagerProphecy->getCache('l10n')->willReturn($cacheFrontendProphecy->reveal());
$cacheFrontendProphecy->get(Argument::cetera())->willReturn(false);
$cacheFrontendProphecy->set(Argument::cetera())->willReturn(null);
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()));
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()), $cacheFrontendProphecy->reveal());
$languageServiceFactoryProphecy = $this->prophesize(LanguageServiceFactory::class);
$languageServiceFactoryProphecy->createFromSiteLanguage(Argument::any())->willReturn($languageService);
GeneralUtility::addInstance(LanguageServiceFactory::class, $languageServiceFactoryProphecy->reveal());
......
......@@ -20,8 +20,8 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\Controller;
use Prophecy\Argument;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Cache\Backend\NullBackend;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
......@@ -157,10 +157,12 @@ class TypoScriptFrontendControllerTest extends UnitTestCase
*/
public function localizationReturnsUnchangedStringIfNotLocallangLabel()
{
$nullCacheBackend = new NullBackend('');
$cacheManager = $this->prophesize(CacheManager::class);
$cacheManager->getCache('l10n')->willReturn($nullCacheBackend);
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManager->reveal()));
$cacheFrontendProphecy = $this->prophesize(FrontendInterface::class);
$cacheFrontendProphecy->get(Argument::cetera())->willReturn(false);
$cacheFrontendProphecy->set(Argument::cetera())->willReturn(null);
$cacheManagerProphecy = $this->prophesize(CacheManager::class);
$cacheManagerProphecy->getCache('l10n')->willReturn($cacheFrontendProphecy->reveal());
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()), $cacheFrontendProphecy->reveal());
$languageServiceFactoryProphecy = $this->prophesize(LanguageServiceFactory::class);
$languageServiceFactoryProphecy->createFromSiteLanguage(Argument::type(SiteLanguage::class))->will(function ($args) use ($languageService) {
$languageService->init($args[0]->getTypo3Language());
......@@ -541,10 +543,12 @@ class TypoScriptFrontendControllerTest extends UnitTestCase
$pageTitleProvider->getPageTitleCache()->willReturn([]);
GeneralUtility::setSingletonInstance(PageTitleProviderManager::class, $pageTitleProvider->reveal());
$nullCacheBackend = new NullBackend('');
$cacheManager = $this->prophesize(CacheManager::class);
$cacheManager->getCache('pages')->willReturn($nullCacheBackend);
GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManager->reveal());
$cacheFrontendProphecy = $this->prophesize(FrontendInterface::class);
$cacheFrontendProphecy->get(Argument::cetera())->willReturn(false);
$cacheFrontendProphecy->set(Argument::cetera())->willReturn(null);
$cacheManagerProphecy = $this->prophesize(CacheManager::class);
$cacheManagerProphecy->getCache('pages')->willReturn($cacheFrontendProphecy->reveal());
GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
$contentObjectRendererProphecy = $this->prophesize(ContentObjectRenderer::class);
$contentObjectRendererProphecy->stdWrapValue(Argument::cetera())->willReturn('');
......@@ -559,18 +563,20 @@ class TypoScriptFrontendControllerTest extends UnitTestCase
*/
public function pageRendererLanguageIsSetToSiteLanguageTypo3LanguageInConstructor(): void
{
$nullCacheBackend = new NullBackend('');
$cacheManager = $this->prophesize(CacheManager::class);
$cacheManager->getCache('pages')->willReturn($nullCacheBackend);
$cacheManager->getCache('l10n')->willReturn($nullCacheBackend);
GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManager->reveal());
$cacheFrontendProphecy = $this->prophesize(FrontendInterface::class);
$cacheFrontendProphecy->get(Argument::cetera())->willReturn(false);
$cacheFrontendProphecy->set(Argument::cetera())->willReturn(null);
$cacheManagerProphecy = $this->prophesize(CacheManager::class);
$cacheManagerProphecy->getCache('pages')->willReturn($cacheFrontendProphecy->reveal());
$cacheManagerProphecy->getCache('l10n')->willReturn($cacheFrontendProphecy->reveal());
GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
$GLOBALS['TYPO3_REQUEST'] = (new ServerRequest('https://www.example.com/'))
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE);
$site = $this->createSiteWithDefaultLanguage([
'locale' => 'fr',
'typo3Language' => 'fr-test',
]);
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManager->reveal()));
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()), $cacheFrontendProphecy->reveal());
$languageServiceFactoryProphecy = $this->prophesize(LanguageServiceFactory::class);
$languageServiceFactoryProphecy->createFromSiteLanguage(Argument::type(SiteLanguage::class))->will(function ($args) use ($languageService) {
$languageService->init($args[0]->getTypo3Language());
......@@ -595,18 +601,20 @@ class TypoScriptFrontendControllerTest extends UnitTestCase
*/
public function languageServiceIsSetUpWithSiteLanguageTypo3LanguageInConstructor(): void
{
$nullCacheBackend = new NullBackend('');
$cacheManager = $this->prophesize(CacheManager::class);
$cacheManager->getCache('pages')->willReturn($nullCacheBackend);
$cacheManager->getCache('l10n')->willReturn($nullCacheBackend);
GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManager->reveal());
$cacheFrontendProphecy = $this->prophesize(FrontendInterface::class);
$cacheFrontendProphecy->get(Argument::cetera())->willReturn(false);
$cacheFrontendProphecy->set(Argument::cetera())->willReturn(null);
$cacheManagerProphecy = $this->prophesize(CacheManager::class);
$cacheManagerProphecy->getCache('pages')->willReturn($cacheFrontendProphecy->reveal());
$cacheManagerProphecy->getCache('l10n')->willReturn($cacheFrontendProphecy->reveal());
GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
$GLOBALS['TYPO3_REQUEST'] = (new ServerRequest('https://www.example.com/'))
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE);
$site = $this->createSiteWithDefaultLanguage([
'locale' => 'fr',
'typo3Language' => 'fr',
]);
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManager->reveal()));
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()), $cacheFrontendProphecy->reveal());
$languageServiceFactoryProphecy = $this->prophesize(LanguageServiceFactory::class);
$languageServiceFactoryProphecy->createFromSiteLanguage(Argument::type(SiteLanguage::class))->will(function ($args) use ($languageService) {
$languageService->init($args[0]->getTypo3Language());
......@@ -636,12 +644,14 @@ class TypoScriptFrontendControllerTest extends UnitTestCase
*/
public function mountPointParameterContainsOnlyValidMPValues(): void
{
$nullCacheBackend = new NullBackend('');
$cacheManager = $this->prophesize(CacheManager::class);
$cacheManager->getCache('pages')->willReturn($nullCacheBackend);
$cacheManager->getCache('l10n')->willReturn($nullCacheBackend);
GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManager->reveal());
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManager->reveal()));
$cacheFrontendProphecy = $this->prophesize(FrontendInterface::class);
$cacheFrontendProphecy->get(Argument::cetera())->willReturn(false);
$cacheFrontendProphecy->set(Argument::cetera())->willReturn(null);
$cacheManagerProphecy = $this->prophesize(CacheManager::class);
$cacheManagerProphecy->getCache('pages')->willReturn($cacheFrontendProphecy->reveal());
$cacheManagerProphecy->getCache('l10n')->willReturn($cacheFrontendProphecy->reveal());
GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
$languageService = new LanguageService(new Locales(), new LocalizationFactory(new LanguageStore(), $cacheManagerProphecy->reveal()), $cacheFrontendProphecy->reveal());
$languageServiceFactoryProphecy = $this->prophesize(LanguageServiceFactory::class);
$languageServiceFactoryProphecy->createFromSiteLanguage(Argument::type(SiteLanguage::class))->will(function ($args) use ($languageService) {
$languageService->init($args[0]->getTypo3Language());
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment