From b677ffbd0be8af6cdab86cdf0c42163cb566bd32 Mon Sep 17 00:00:00 2001 From: Benjamin Franzke <bfr@qbus.de> Date: Thu, 17 Feb 2022 18:30:31 +0100 Subject: [PATCH] [TASK] Use dependency injection in PageRenderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add dependency injection to PageRenderer and it's direct dependencies. Most prominently this avoids the static assets-cache injection from TYPO3 bootstrap and refactors to regular injection instead. ResourceCompressor is adapted to be delay it's initialization to be injectable without immediate side-effects. Backend Router and UriBuilder are not injected because they are a) only needed in backend context and b) should ideally be moved out of the PageRenderer into EXT:backend anyway. Resolves: #97030 Releases: main Change-Id: I8e72e5f87a095372439c3cd8106318ae272c6ce0 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73594 Tested-by: core-ci <typo3@b13.com> Tested-by: Benni Mack <benni@typo3.org> Tested-by: Stefan Bürk <stefan@buerk.tech> Tested-by: Benjamin Franzke <bfr@qbus.de> Reviewed-by: Benni Mack <benni@typo3.org> Reviewed-by: Stefan Bürk <stefan@buerk.tech> Reviewed-by: Benjamin Franzke <bfr@qbus.de> --- Build/phpstan/phpstan-baseline.neon | 5 - .../sysext/core/Classes/Core/BootService.php | 3 - typo3/sysext/core/Classes/Core/Bootstrap.php | 2 - .../sysext/core/Classes/Page/PageRenderer.php | 151 +++++++---------- .../Classes/Resource/ResourceCompressor.php | 19 ++- .../Service/MarkerBasedTemplateService.php | 40 ++--- typo3/sysext/core/Configuration/Services.yaml | 10 ++ .../Functional/Page/PageRendererTest.php | 34 +++- .../Unit/Page/PageRendererFactoryTrait.php | 79 +++++++++ .../core/Tests/Unit/Page/PageRendererTest.php | 23 ++- .../ResourceCompressorIntegrationTest.php | 15 +- .../MarkerBasedTemplateServiceTest.php | 11 +- .../ContentObject/ImageContentObject.php | 11 +- .../FluidTemplateContentObjectTest.php | 153 +++++++++++++++++- .../ContentObject/ImageContentObjectTest.php | 34 ++-- .../Menu/AbstractMenuContentObjectTest.php | 7 + .../TypoScriptFrontendControllerTest.php | 10 +- .../Tests/Unit/Plugin/AbstractPluginTest.php | 6 + 18 files changed, 431 insertions(+), 182 deletions(-) create mode 100644 typo3/sysext/core/Tests/Unit/Page/PageRendererFactoryTrait.php diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon index 72db186eba44..247eaf864bc5 100644 --- a/Build/phpstan/phpstan-baseline.neon +++ b/Build/phpstan/phpstan-baseline.neon @@ -1320,11 +1320,6 @@ parameters: count: 1 path: ../../typo3/sysext/core/Classes/Page/PageRenderer.php - - - message: "#^Static property TYPO3\\\\CMS\\\\Core\\\\Page\\\\PageRenderer\\:\\:\\$cache \\(TYPO3\\\\CMS\\\\Core\\\\Cache\\\\Frontend\\\\FrontendInterface\\) on left side of \\?\\? is not nullable\\.$#" - count: 1 - path: ../../typo3/sysext/core/Classes/Page/PageRenderer.php - - message: "#^Strict comparison using \\=\\=\\= between array\\{settings\\?\\: non\\-empty\\-array, lang\\?\\: non\\-empty\\-array\\}&non\\-empty\\-array and array\\{\\} will always evaluate to false\\.$#" count: 1 diff --git a/typo3/sysext/core/Classes/Core/BootService.php b/typo3/sysext/core/Classes/Core/BootService.php index c54dad2628bb..1915b1b7cf75 100644 --- a/typo3/sysext/core/Classes/Core/BootService.php +++ b/typo3/sysext/core/Classes/Core/BootService.php @@ -22,7 +22,6 @@ use Psr\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Core\Event\BootCompletedEvent; use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder; use TYPO3\CMS\Core\Package\PackageManager; -use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -126,8 +125,6 @@ class BootService $beUserBackup = $GLOBALS['BE_USER'] ?? null; $container->get('boot.state')->complete = false; - $assetsCache = $container->get('cache.assets'); - PageRenderer::setCache($assetsCache); $eventDispatcher = $container->get(EventDispatcherInterface::class); ExtensionManagementUtility::setEventDispatcher($eventDispatcher); Bootstrap::loadTypo3LoadedExtAndExtLocalconf($allowCaching, $container->get('cache.core')); diff --git a/typo3/sysext/core/Classes/Core/Bootstrap.php b/typo3/sysext/core/Classes/Core/Bootstrap.php index d7fb7c9548da..490457b8d0df 100644 --- a/typo3/sysext/core/Classes/Core/Bootstrap.php +++ b/typo3/sysext/core/Classes/Core/Bootstrap.php @@ -44,7 +44,6 @@ use TYPO3\CMS\Core\Package\Cache\PackageCacheInterface; use TYPO3\CMS\Core\Package\Cache\PackageStatesPackageCache; use TYPO3\CMS\Core\Package\FailsafePackageManager; use TYPO3\CMS\Core\Package\PackageManager; -use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Service\DependencyOrderingService; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -155,7 +154,6 @@ class Bootstrap } $eventDispatcher = $container->get(EventDispatcherInterface::class); - PageRenderer::setCache($assetsCache); ExtensionManagementUtility::setEventDispatcher($eventDispatcher); static::loadTypo3LoadedExtAndExtLocalconf(true, $coreCache); static::unsetReservedGlobalVariables(); diff --git a/typo3/sysext/core/Classes/Page/PageRenderer.php b/typo3/sysext/core/Classes/Page/PageRenderer.php index 7d9321eb5648..cd7ed9ac9a30 100644 --- a/typo3/sysext/core/Classes/Page/PageRenderer.php +++ b/typo3/sysext/core/Classes/Page/PageRenderer.php @@ -18,7 +18,6 @@ namespace TYPO3\CMS\Core\Page; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Routing\Router; use TYPO3\CMS\Backend\Routing\UriBuilder; -use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Http\ApplicationType; @@ -80,11 +79,6 @@ class PageRenderer implements SingletonInterface */ protected $moveJsFromHeaderToFooter = false; - /** - * @var Locales - */ - protected $locales; - /** * The language key * Two character string or 'default' @@ -101,11 +95,6 @@ class PageRenderer implements SingletonInterface */ protected $languageDependencies = []; - /** - * @var ResourceCompressor - */ - protected $compressor; - // Arrays containing associative array for the included files /** * @var array<string, array> @@ -331,30 +320,21 @@ class PageRenderer implements SingletonInterface */ protected $endingSlash = ''; - /** - * @var MetaTagManagerRegistry - */ - protected $metaTagRegistry; - protected JavaScriptRenderer $javaScriptRenderer; - /** - * @var FrontendInterface - */ - protected static $cache; - - /** - * @param string $templateFile Declare the used template file. Omit this parameter will use default template - */ - public function __construct($templateFile = '') - { + public function __construct( + protected readonly FrontendInterface $assetsCache, + protected readonly Locales $locales, + protected readonly MarkerBasedTemplateService $templateService, + protected readonly MetaTagManagerRegistry $metaTagRegistry, + protected readonly PackageManager $packageManager, + protected readonly AssetRenderer $assetRenderer, + protected readonly ResourceCompressor $resourceCompressor, + protected readonly RelativeCssPathFixer $relativeCssPathFixer, + protected readonly LocalizationFactory $localizationFactory, + ) { $this->reset(); - $this->locales = GeneralUtility::makeInstance(Locales::class); - if ($templateFile !== '') { - $this->templateFile = $templateFile; - } - $this->metaTagRegistry = GeneralUtility::makeInstance(MetaTagManagerRegistry::class); $this->setMetaTag('name', 'generator', 'TYPO3 CMS'); } @@ -366,7 +346,16 @@ class PageRenderer implements SingletonInterface { foreach ($state as $var => $value) { switch ($var) { + case 'assetsCache': case 'locales': + case 'packageManager': + case 'assetRenderer': + case 'templateService': + case 'resourceCompressor': + case 'relativeCssPathFixer': + case 'localizationFactory': + case 'responseFactory': + case 'streamFactory': break; case 'metaTagRegistry': $this->metaTagRegistry->updateState($value); @@ -390,7 +379,16 @@ class PageRenderer implements SingletonInterface $state = []; foreach (get_object_vars($this) as $var => $value) { switch ($var) { + case 'assetsCache': case 'locales': + case 'packageManager': + case 'assetRenderer': + case 'templateService': + case 'resourceCompressor': + case 'relativeCssPathFixer': + case 'localizationFactory': + case 'responseFactory': + case 'streamFactory': break; case 'metaTagRegistry': $state[$var] = $this->metaTagRegistry->getState(); @@ -406,14 +404,6 @@ class PageRenderer implements SingletonInterface return $state; } - /** - * @param FrontendInterface $cache - */ - public static function setCache(FrontendInterface $cache) - { - static::$cache = $cache; - } - public function getJavaScriptRenderer(): JavaScriptRenderer { return $this->javaScriptRenderer; @@ -1356,21 +1346,18 @@ class PageRenderer implements SingletonInterface return; } - $packageManager = GeneralUtility::makeInstance(PackageManager::class); - $packages = $packageManager->getActivePackages(); + $packages = $this->packageManager->getActivePackages(); $isDevelopment = Environment::getContext()->isDevelopment(); - $cacheIdentifier = (new PackageDependentCacheIdentifier($packageManager)) + $cacheIdentifier = (new PackageDependentCacheIdentifier($this->packageManager)) ->withPrefix('RequireJS') ->withAdditionalHashedIdentifier(($isDevelopment ? ':dev' : '') . GeneralUtility::getIndpEnv('TYPO3_REQUEST_SCRIPT')) ->toString(); - /** @var FrontendInterface $cache */ - $cache = static::$cache ?? GeneralUtility::makeInstance(CacheManager::class)->getCache('assets'); - $requireJsConfig = $cache->get($cacheIdentifier); + $requireJsConfig = $this->assetsCache->get($cacheIdentifier); // if we did not get a configuration from the cache, compute and store it in the cache if (!isset($requireJsConfig['internal']) || !isset($requireJsConfig['public'])) { $requireJsConfig = $this->computeRequireJsConfig($isDevelopment, $packages); - $cache->set($cacheIdentifier, $requireJsConfig); + $this->assetsCache->set($cacheIdentifier, $requireJsConfig); } $this->requireJsConfig = array_merge_recursive($this->additionalRequireJsConfig, $requireJsConfig['internal']); @@ -1762,8 +1749,7 @@ class PageRenderer implements SingletonInterface // The page renderer needs a full reset when the page was rendered $this->reset(); - $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class); - return trim($templateService->substituteMarkerArray($template, $markerArray, '###|###')); + return trim($this->templateService->substituteMarkerArray($template, $markerArray, '###|###')); } /** @@ -1797,8 +1783,7 @@ class PageRenderer implements SingletonInterface $this->prepareRendering(); $markerArray = $this->getPreparedMarkerArrayForPageWithUncachedObjects($substituteHash); $template = $this->getTemplate(); - $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class); - return trim($templateService->substituteMarkerArray($template, $markerArray, '###|###')); + return trim($this->templateService->substituteMarkerArray($template, $markerArray, '###|###')); } /** @@ -1888,16 +1873,15 @@ class PageRenderer implements SingletonInterface $jsInline = ''; } // Use AssetRenderer to inject all JavaScripts and CSS files - $assetRenderer = GeneralUtility::makeInstance(AssetRenderer::class); - $jsInline .= $assetRenderer->renderInlineJavaScript(true); - $jsFooterInline .= $assetRenderer->renderInlineJavaScript(); - $jsFiles .= $assetRenderer->renderJavaScript(true); - $jsFooterFiles .= $assetRenderer->renderJavaScript(); - $cssInline .= $assetRenderer->renderInlineStyleSheets(true); + $jsInline .= $this->assetRenderer->renderInlineJavaScript(true); + $jsFooterInline .= $this->assetRenderer->renderInlineJavaScript(); + $jsFiles .= $this->assetRenderer->renderJavaScript(true); + $jsFooterFiles .= $this->assetRenderer->renderJavaScript(); + $cssInline .= $this->assetRenderer->renderInlineStyleSheets(true); // append inline CSS to footer (as there is no cssFooterInline) - $jsFooterFiles .= $assetRenderer->renderInlineStyleSheets(); - $cssLibs .= $assetRenderer->renderStyleSheets(true, $this->endingSlash); - $cssFiles .= $assetRenderer->renderStyleSheets(false, $this->endingSlash); + $jsFooterFiles .= $this->assetRenderer->renderInlineStyleSheets(); + $cssLibs .= $this->assetRenderer->renderStyleSheets(true, $this->endingSlash); + $cssFiles .= $this->assetRenderer->renderStyleSheets(false, $this->endingSlash); $this->executePostRenderHook($jsLibs, $jsFiles, $jsFooterFiles, $cssLibs, $cssFiles, $jsInline, $cssInline, $jsFooterInline, $jsFooterLibs); return [$jsLibs, $jsFiles, $jsFooterFiles, $cssLibs, $cssFiles, $jsInline, $cssInline, $jsFooterInline, $jsFooterLibs]; @@ -2406,9 +2390,6 @@ class PageRenderer implements SingletonInterface */ protected function readLLfile($fileRef) { - /** @var LocalizationFactory $languageFactory */ - $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class); - if ($this->lang !== 'default') { $languages = array_reverse($this->languageDependencies); // At least we need to have English @@ -2421,7 +2402,7 @@ class PageRenderer implements SingletonInterface $localLanguage = []; foreach ($languages as $language) { - $tempLL = $languageFactory->getParsedData($fileRef, $language); + $tempLL = $this->localizationFactory->getParsedData($fileRef, $language); $localLanguage['default'] = $tempLL['default']; if (!isset($localLanguage[$this->lang])) { @@ -2469,9 +2450,9 @@ class PageRenderer implements SingletonInterface ]; GeneralUtility::callUserFunction($GLOBALS['TYPO3_CONF_VARS'][$this->getApplicationType()]['jsConcatenateHandler'], $params, $this); } else { - $this->jsLibs = $this->getCompressor()->concatenateJsFiles($this->jsLibs); - $this->jsFiles = $this->getCompressor()->concatenateJsFiles($this->jsFiles); - $this->jsFooterFiles = $this->getCompressor()->concatenateJsFiles($this->jsFooterFiles); + $this->jsLibs = $this->resourceCompressor->concatenateJsFiles($this->jsLibs); + $this->jsFiles = $this->resourceCompressor->concatenateJsFiles($this->jsFiles); + $this->jsFooterFiles = $this->resourceCompressor->concatenateJsFiles($this->jsFooterFiles); } } } @@ -2492,8 +2473,8 @@ class PageRenderer implements SingletonInterface ]; GeneralUtility::callUserFunction($GLOBALS['TYPO3_CONF_VARS'][$this->getApplicationType()]['cssConcatenateHandler'], $params, $this); } else { - $this->cssLibs = $this->getCompressor()->concatenateCssFiles($this->cssLibs); - $this->cssFiles = $this->getCompressor()->concatenateCssFiles($this->cssFiles); + $this->cssLibs = $this->resourceCompressor->concatenateCssFiles($this->cssLibs); + $this->cssFiles = $this->resourceCompressor->concatenateCssFiles($this->cssFiles); } } } @@ -2524,8 +2505,8 @@ class PageRenderer implements SingletonInterface ]; GeneralUtility::callUserFunction($GLOBALS['TYPO3_CONF_VARS'][$this->getApplicationType()]['cssCompressHandler'], $params, $this); } else { - $this->cssLibs = $this->getCompressor()->compressCssFiles($this->cssLibs); - $this->cssFiles = $this->getCompressor()->compressCssFiles($this->cssFiles); + $this->cssLibs = $this->resourceCompressor->compressCssFiles($this->cssLibs); + $this->cssFiles = $this->resourceCompressor->compressCssFiles($this->cssFiles); } } } @@ -2552,29 +2533,16 @@ class PageRenderer implements SingletonInterface // Traverse the arrays, compress files foreach ($this->jsInline ?? [] as $name => $properties) { if ($properties['compress'] ?? false) { - $this->jsInline[$name]['code'] = $this->getCompressor()->compressJavaScriptSource($properties['code']); + $this->jsInline[$name]['code'] = $this->resourceCompressor->compressJavaScriptSource($properties['code']); } } - $this->jsLibs = $this->getCompressor()->compressJsFiles($this->jsLibs); - $this->jsFiles = $this->getCompressor()->compressJsFiles($this->jsFiles); - $this->jsFooterFiles = $this->getCompressor()->compressJsFiles($this->jsFooterFiles); + $this->jsLibs = $this->resourceCompressor->compressJsFiles($this->jsLibs); + $this->jsFiles = $this->resourceCompressor->compressJsFiles($this->jsFiles); + $this->jsFooterFiles = $this->resourceCompressor->compressJsFiles($this->jsFooterFiles); } } } - /** - * Returns instance of \TYPO3\CMS\Core\Resource\ResourceCompressor - * - * @return ResourceCompressor - */ - protected function getCompressor() - { - if ($this->compressor === null) { - $this->compressor = GeneralUtility::makeInstance(ResourceCompressor::class); - } - return $this->compressor; - } - /** * Processes a Javascript file dependent on the current context * @@ -2587,7 +2555,7 @@ class PageRenderer implements SingletonInterface { $filename = $this->getStreamlinedFileName($filename, false); if ($this->compressJavascript) { - $filename = $this->getCompressor()->compressJsFile($filename); + $filename = $this->resourceCompressor->compressJsFile($filename); } elseif ($this->getApplicationType() === 'FE') { $filename = GeneralUtility::createVersionNumberedFilename($filename); } @@ -2764,7 +2732,7 @@ class PageRenderer implements SingletonInterface if ($cssInline === false) { return ''; } - $cssInlineFix = $this->getPathFixer()->fixRelativeUrlPaths($cssInline, '/' . PathUtility::dirname($file) . '/'); + $cssInlineFix = $this->relativeCssPathFixer->fixRelativeUrlPaths($cssInline, '/' . PathUtility::dirname($file) . '/'); return '<style' . ' media="' . htmlspecialchars($properties['media']) . '"' . ($properties['title'] ? ' title="' . htmlspecialchars($properties['title']) . '"' : '') @@ -2774,11 +2742,6 @@ class PageRenderer implements SingletonInterface . '-->' . LF . '/*]]>*/' . LF . '</style>' . LF; } - protected function getPathFixer(): RelativeCssPathFixer - { - return GeneralUtility::makeInstance(RelativeCssPathFixer::class); - } - /** * String 'FE' if in FrontendApplication, 'BE' otherwise (also in CLI without request object) * diff --git a/typo3/sysext/core/Classes/Resource/ResourceCompressor.php b/typo3/sysext/core/Classes/Resource/ResourceCompressor.php index b1f358a8c6c6..6fbf5badb219 100644 --- a/typo3/sysext/core/Classes/Resource/ResourceCompressor.php +++ b/typo3/sysext/core/Classes/Resource/ResourceCompressor.php @@ -63,11 +63,13 @@ class ResourceCompressor FileETag MTime Size </FilesMatch>'; - /** - * Constructor - */ - public function __construct() + protected bool $initialized = false; + + protected function initialize(): void { + if ($this->initialized) { + return; + } // we check for existence of our targetDirectory if (!is_dir(Environment::getPublicPath() . '/' . $this->targetDirectory)) { GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/' . $this->targetDirectory); @@ -96,6 +98,8 @@ class ResourceCompressor } } $this->setRootPath($applicationType === 'BE' ? Environment::getBackendPath() . '/' : Environment::getPublicPath() . '/'); + + $this->initialized = true; } /** @@ -118,6 +122,7 @@ class ResourceCompressor */ public function concatenateCssFiles(array $cssFiles) { + $this->initialize(); $filesToIncludeByType = ['all' => []]; foreach ($cssFiles as $key => $fileOptions) { // no concatenation allowed for this file, so continue @@ -165,6 +170,7 @@ class ResourceCompressor */ public function concatenateJsFiles(array $jsFiles) { + $this->initialize(); $concatenatedJsFileIsAsync = false; $allFilesToConcatenateAreAsync = true; $filesToInclude = []; @@ -317,6 +323,7 @@ class ResourceCompressor */ public function compressCssFiles(array $cssFiles) { + $this->initialize(); $filesAfterCompression = []; foreach ($cssFiles as $key => $fileOptions) { // if compression is enabled @@ -346,6 +353,7 @@ class ResourceCompressor */ public function compressCssFile($filename) { + $this->initialize(); // generate the unique name of the file $filenameAbsolute = GeneralUtility::resolveBackPath($this->rootPath . $this->getFilenameFromMainDir($filename)); if (@file_exists($filenameAbsolute)) { @@ -378,6 +386,7 @@ class ResourceCompressor */ public function compressJsFiles(array $jsFiles) { + $this->initialize(); $filesAfterCompression = []; foreach ($jsFiles as $fileName => $fileOptions) { // If compression is enabled @@ -401,6 +410,7 @@ class ResourceCompressor */ public function compressJsFile($filename) { + $this->initialize(); // generate the unique name of the file $filenameAbsolute = GeneralUtility::resolveBackPath($this->rootPath . $this->getFilenameFromMainDir($filename)); if (@file_exists($filenameAbsolute)) { @@ -619,6 +629,7 @@ class ResourceCompressor public function compressJavaScriptSource(string $javaScriptSourceCode): string { + $this->initialize(); $fakeThis = null; foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_div.php']['minifyJavaScript'] ?? [] as $hookMethod) { try { diff --git a/typo3/sysext/core/Classes/Service/MarkerBasedTemplateService.php b/typo3/sysext/core/Classes/Service/MarkerBasedTemplateService.php index a4561738833e..34f38548efad 100644 --- a/typo3/sysext/core/Classes/Service/MarkerBasedTemplateService.php +++ b/typo3/sysext/core/Classes/Service/MarkerBasedTemplateService.php @@ -15,7 +15,7 @@ namespace TYPO3\CMS\Core\Service; -use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -25,6 +25,12 @@ use TYPO3\CMS\Core\Utility\MathUtility; */ class MarkerBasedTemplateService { + public function __construct( + protected readonly FrontendInterface $hashCache, + protected readonly FrontendInterface $runtimeCache, + ) { + } + /** * Returns the first subpart encapsulated in the marker, $marker * (possibly present in $content as a HTML comment) @@ -355,7 +361,6 @@ class MarkerBasedTemplateService */ public function substituteMarkerArrayCached($content, array $markContentArray = null, array $subpartContentArray = null, array $wrappedSubpartContentArray = null) { - $runtimeCache = $this->getRuntimeCache(); // If not arrays then set them if ($markContentArray === null) { // Plain markers @@ -378,16 +383,15 @@ class MarkerBasedTemplateService } asort($keysToReplace); $storeKey = md5('substituteMarkerArrayCached_storeKey:' . serialize([$content, $keysToReplace])); - $fromCache = $runtimeCache->get($storeKey); + $fromCache = $this->runtimeCache->get($storeKey); if ($fromCache) { $storeArr = $fromCache; } else { - $cache = $this->getCache(); - $storeArrDat = $cache->get($storeKey); + $storeArrDat = $this->hashCache->get($storeKey); if (is_array($storeArrDat)) { $storeArr = $storeArrDat; // Setting the data in the first level cache - $runtimeCache->set($storeKey, $storeArr); + $this->runtimeCache->set($storeKey, $storeArr); } else { // Finding subparts and substituting them with the subpart as a marker foreach ($sPkeys as $sPK) { @@ -419,9 +423,9 @@ class MarkerBasedTemplateService $storeArr['c'] = preg_split($regex, $content); // contains all content parts around markers $storeArr['k'] = $wrappedKeys; // contains all markers incl. ### // Setting the data inside the second-level cache - $runtimeCache->set($storeKey, $storeArr); + $this->runtimeCache->set($storeKey, $storeArr); // Storing the cached data permanently - $cache->set($storeKey, $storeArr, ['substMarkArrayCached'], 0); + $this->hashCache->set($storeKey, $storeArr, ['substMarkArrayCached'], 0); } } } @@ -507,24 +511,4 @@ class MarkerBasedTemplateService } return $markContentArray; } - - /** - * Second-level cache - * - * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface - */ - protected function getCache() - { - return GeneralUtility::makeInstance(CacheManager::class)->getCache('hash'); - } - - /** - * First-level cache (runtime cache) - * - * @return \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface - */ - protected function getRuntimeCache() - { - return GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime'); - } } diff --git a/typo3/sysext/core/Configuration/Services.yaml b/typo3/sysext/core/Configuration/Services.yaml index 0ef00aa8d515..920e463c30aa 100644 --- a/typo3/sysext/core/Configuration/Services.yaml +++ b/typo3/sysext/core/Configuration/Services.yaml @@ -210,6 +210,10 @@ services: method: 'warmupCaches' event: TYPO3\CMS\Core\Cache\Event\CacheWarmupEvent + TYPO3\CMS\Core\Page\PageRenderer: + arguments: + $assetsCache: '@cache.assets' + TYPO3\CMS\Core\Page\AssetRenderer: public: true arguments: @@ -281,6 +285,12 @@ services: TYPO3\CMS\Core\Domain\Access\RecordAccessVoter: public: true + TYPO3\CMS\Core\Service\MarkerBasedTemplateService: + public: true + arguments: + $hashCache: '@cache.assets' + $runtimeCache: '@cache.runtime' + # Core caches, cache.core and cache.assets are injected as early # entries in TYPO3\CMS\Core\Core\Bootstrap and therefore omitted here cache.hash: diff --git a/typo3/sysext/core/Tests/Functional/Page/PageRendererTest.php b/typo3/sysext/core/Tests/Functional/Page/PageRendererTest.php index f7f0ad0f7ee6..8799e8879b6e 100644 --- a/typo3/sysext/core/Tests/Functional/Page/PageRendererTest.php +++ b/typo3/sysext/core/Tests/Functional/Page/PageRendererTest.php @@ -25,7 +25,15 @@ use TYPO3\CMS\Core\Authentication\IpLocker; use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder; use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Localization\LanguageServiceFactory; +use TYPO3\CMS\Core\Localization\Locales; +use TYPO3\CMS\Core\Localization\LocalizationFactory; +use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry; +use TYPO3\CMS\Core\Package\PackageManager; +use TYPO3\CMS\Core\Page\AssetRenderer; use TYPO3\CMS\Core\Page\PageRenderer; +use TYPO3\CMS\Core\Resource\RelativeCssPathFixer; +use TYPO3\CMS\Core\Resource\ResourceCompressor; +use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; use TYPO3\CMS\Core\Session\Backend\SessionBackendInterface; use TYPO3\CMS\Core\Session\UserSessionManager; use TYPO3\CMS\Core\Utility\StringUtility; @@ -40,6 +48,22 @@ class PageRendererTest extends FunctionalTestCase */ protected bool $initializeDatabase = false; + protected function createPageRenderer(): PageRenderer + { + $container = $this->getContainer(); + return new PageRenderer( + $container->get('cache.assets'), + $container->get(Locales::class), + $container->get(MarkerBasedTemplateService::class), + $container->get(MetaTagManagerRegistry::class), + $container->get(PackageManager::class), + $container->get(AssetRenderer::class), + new ResourceCompressor(), + new RelativeCssPathFixer(), + $container->get(LocalizationFactory::class), + ); + } + /** * @test */ @@ -47,7 +71,7 @@ class PageRendererTest extends FunctionalTestCase { $GLOBALS['TYPO3_REQUEST'] = (new ServerRequest('https://www.example.com/')) ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); - $subject = new PageRenderer(); + $subject = $this->createPageRenderer(); $subject->setCharSet('utf-8'); $subject->setLanguage('default'); @@ -152,7 +176,7 @@ class PageRendererTest extends FunctionalTestCase self::assertStringContainsString('<meta property="og:image" content="/path/to/image1.jpg" />', $renderedString); self::assertStringContainsString('<meta property="og:image" content="/path/to/image2.jpg" />', $renderedString); - $stateBasedSubject = new PageRenderer(); + $stateBasedSubject = $this->createPageRenderer(); $stateBasedSubject->updateState(unserialize($state, ['allowed_classes' => false])); $stateBasedRenderedString = $stateBasedSubject->render(); self::assertStringContainsString($expectedPrologueString, $stateBasedRenderedString); @@ -199,7 +223,7 @@ class PageRendererTest extends FunctionalTestCase { $GLOBALS['TYPO3_REQUEST'] = (new ServerRequest('https://www.example.com/')) ->withAttribute('applicationType', $requestType); - $subject = new PageRenderer(); + $subject = $this->createPageRenderer(); $subject->setCharSet('utf-8'); $subject->setLanguage('default'); @@ -286,7 +310,7 @@ class PageRendererTest extends FunctionalTestCase { $GLOBALS['TYPO3_REQUEST'] = (new ServerRequest('https://www.example.com/')) ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); - $subject = new PageRenderer(); + $subject = $this->createPageRenderer(); $subject->setCharSet('utf-8'); $subject->setLanguage('default'); @@ -386,7 +410,7 @@ class PageRendererTest extends FunctionalTestCase $GLOBALS['TYPO3_REQUEST'] = (new ServerRequest()) ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE); - $subject = new PageRenderer(); + $subject = $this->createPageRenderer(); $subject->setCharSet('utf-8'); $subject->setLanguage('default'); diff --git a/typo3/sysext/core/Tests/Unit/Page/PageRendererFactoryTrait.php b/typo3/sysext/core/Tests/Unit/Page/PageRendererFactoryTrait.php new file mode 100644 index 000000000000..03f9ef9cf1d2 --- /dev/null +++ b/typo3/sysext/core/Tests/Unit/Page/PageRendererFactoryTrait.php @@ -0,0 +1,79 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\Tests\Unit\Page; + +use Prophecy\PhpUnit\ProphecyTrait; +use Psr\Container\ContainerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; +use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; +use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; +use TYPO3\CMS\Core\EventDispatcher\ListenerProvider; +use TYPO3\CMS\Core\Localization\LanguageStore; +use TYPO3\CMS\Core\Localization\Locales; +use TYPO3\CMS\Core\Localization\LocalizationFactory; +use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry; +use TYPO3\CMS\Core\Package\PackageManager; +use TYPO3\CMS\Core\Page\AssetRenderer; +use TYPO3\CMS\Core\Resource\RelativeCssPathFixer; +use TYPO3\CMS\Core\Resource\ResourceCompressor; +use TYPO3\CMS\Core\Service\DependencyOrderingService; +use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +trait PageRendererFactoryTrait +{ + use ProphecyTrait; + + protected function getPageRendererConstructorArgs( + PackageManager $packageManager = null, + CacheManager $cacheManager = null, + ): array { + $packageManager ??= new PackageManager(new DependencyOrderingService()); + $cacheManagerProphecy = $this->prophesize(CacheManager::class); + $cacheManager ??= $cacheManagerProphecy->reveal(); + + /** + * prepare an EventDispatcher for ::makeInstance(AssetRenderer) + * @see \TYPO3\CMS\Core\Page\PageRenderer::renderJavaScriptAndCss + */ + GeneralUtility::setSingletonInstance( + EventDispatcherInterface::class, + new EventDispatcher( + new ListenerProvider($this->createMock(ContainerInterface::class)) + ) + ); + + $assetRenderer = new AssetRenderer(); + + return [ + new NullFrontend('assets'), + new Locales(), + new MarkerBasedTemplateService( + new NullFrontend('hash'), + new NullFrontend('runtime'), + ), + new MetaTagManagerRegistry(), + $packageManager, + $assetRenderer, + new ResourceCompressor(), + new RelativeCssPathFixer(), + new LocalizationFactory(new LanguageStore($packageManager), $cacheManager), + ]; + } +} diff --git a/typo3/sysext/core/Tests/Unit/Page/PageRendererTest.php b/typo3/sysext/core/Tests/Unit/Page/PageRendererTest.php index 8a6e8ab629e0..ba2c5c520a11 100644 --- a/typo3/sysext/core/Tests/Unit/Page/PageRendererTest.php +++ b/typo3/sysext/core/Tests/Unit/Page/PageRendererTest.php @@ -33,6 +33,7 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class PageRendererTest extends UnitTestCase { use ProphecyTrait; + use PageRendererFactoryTrait; /** * @var bool Reset singletons created by subject @@ -56,6 +57,7 @@ class PageRendererTest extends UnitTestCase { /** @var PageRenderer|AccessibleObjectInterface $pageRenderer */ $pageRenderer = $this->getMockBuilder(PageRenderer::class) + ->setConstructorArgs($this->getPageRendererConstructorArgs()) ->onlyMethods(['reset', 'prepareRendering', 'renderJavaScriptAndCss', 'getPreparedMarkerArray', 'getTemplate']) ->getMock(); @@ -265,8 +267,11 @@ class PageRendererTest extends UnitTestCase */ public function getAddedMetaTag(): void { - /** @var PageRenderer|\PHPUnit\Framework\MockObject\MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $subject */ - $subject = $this->getAccessibleMock(PageRenderer::class, ['whatDoesThisDo']); + /** @var PageRenderer|\PHPUnit\Framework\MockObject\MockObject */ + $subject = $this->getMockBuilder(PageRenderer::class) + ->setConstructorArgs($this->getPageRendererConstructorArgs()) + ->setMethodsExcept(['setMetaTag', 'getMetaTag']) + ->getMock(); $subject->setMetaTag('nAme', 'Author', 'foobar'); $actualResult = $subject->getMetaTag('naMe', 'AUTHOR'); $expectedResult = [ @@ -282,8 +287,11 @@ class PageRendererTest extends UnitTestCase */ public function overrideMetaTag(): void { - /** @var PageRenderer|\PHPUnit\Framework\MockObject\MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $subject */ - $subject = $this->getAccessibleMock(PageRenderer::class, ['whatDoesThisDo']); + /** @var PageRenderer|\PHPUnit\Framework\MockObject\MockObject */ + $subject = $this->getMockBuilder(PageRenderer::class) + ->setConstructorArgs($this->getPageRendererConstructorArgs()) + ->setMethodsExcept(['setMetaTag', 'getMetaTag']) + ->getMock(); $subject->setMetaTag('nAme', 'Author', 'Axel Foley'); $subject->setMetaTag('nAme', 'Author', 'foobar'); $actualResult = $subject->getMetaTag('naMe', 'AUTHOR'); @@ -300,8 +308,11 @@ class PageRendererTest extends UnitTestCase */ public function unsetAddedMetaTag(): void { - /** @var PageRenderer|\PHPUnit\Framework\MockObject\MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface $subject */ - $subject = $this->getAccessibleMock(PageRenderer::class, ['whatDoesThisDo']); + /** @var PageRenderer|\PHPUnit\Framework\MockObject\MockObject */ + $subject = $this->getMockBuilder(PageRenderer::class) + ->setConstructorArgs($this->getPageRendererConstructorArgs()) + ->setMethodsExcept(['setMetaTag', 'removeMetaTag', 'getMetaTag']) + ->getMock(); $subject->setMetaTag('nAme', 'Author', 'foobar'); $subject->removeMetaTag('naMe', 'AUTHOR'); $actualResult = $subject->getMetaTag('naMe', 'AUTHOR'); diff --git a/typo3/sysext/core/Tests/Unit/Resource/ResourceCompressorIntegrationTest.php b/typo3/sysext/core/Tests/Unit/Resource/ResourceCompressorIntegrationTest.php index 012c2bb9f092..a797ced922d7 100644 --- a/typo3/sysext/core/Tests/Unit/Resource/ResourceCompressorIntegrationTest.php +++ b/typo3/sysext/core/Tests/Unit/Resource/ResourceCompressorIntegrationTest.php @@ -51,9 +51,10 @@ class ResourceCompressorIntegrationTest extends BaseTestCase /** * @test */ - public function constructorCreatesTargetDirectory(): void + public function initializeCreatesTargetDirectory(): void { - $this->resourceCompressor = new TestableResourceCompressor(); + $this->resourceCompressor = $this->getAccessibleMock(TestableResourceCompressor::class, null); + $this->resourceCompressor->_call('initialize'); $dir = Environment::getPublicPath() . '/' . $this->resourceCompressor->getTargetDirectory(); self::assertFileExists($dir); } @@ -61,10 +62,11 @@ class ResourceCompressorIntegrationTest extends BaseTestCase /** * @test */ - public function constructorCreatesHtaccessFileIfSet(): void + public function initializeCreatesHtaccessFileIfSet(): void { $GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess'] = true; - $this->resourceCompressor = new TestableResourceCompressor(); + $this->resourceCompressor = $this->getAccessibleMock(TestableResourceCompressor::class, null); + $this->resourceCompressor->_call('initialize'); $htaccessPath = Environment::getPublicPath() . '/' . $this->resourceCompressor->getTargetDirectory() . '.htaccess'; self::assertStringEqualsFile($htaccessPath, $this->resourceCompressor->getHtaccessTemplate()); } @@ -72,10 +74,11 @@ class ResourceCompressorIntegrationTest extends BaseTestCase /** * @test */ - public function constructorDoesNotCreateHtaccessFileIfSetToFalse(): void + public function initializeDoesNotCreateHtaccessFileIfSetToFalse(): void { $GLOBALS['TYPO3_CONF_VARS']['SYS']['generateApacheHtaccess'] = false; - $this->resourceCompressor = new TestableResourceCompressor(); + $this->resourceCompressor = $this->getAccessibleMock(TestableResourceCompressor::class, null); + $this->resourceCompressor->_call('initialize'); $htaccessPath = Environment::getPublicPath() . '/' . $this->resourceCompressor->getTargetDirectory() . '.htaccess'; // @todo remove condition and else branch as soon as phpunit v8 goes out of support if (method_exists($this, 'assertFileDoesNotExist')) { diff --git a/typo3/sysext/core/Tests/Unit/Service/MarkerBasedTemplateServiceTest.php b/typo3/sysext/core/Tests/Unit/Service/MarkerBasedTemplateServiceTest.php index 6ad1d7a1dc4b..e217485b6afb 100644 --- a/typo3/sysext/core/Tests/Unit/Service/MarkerBasedTemplateServiceTest.php +++ b/typo3/sysext/core/Tests/Unit/Service/MarkerBasedTemplateServiceTest.php @@ -17,12 +17,9 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\Tests\Unit\Service; -use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; -use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; /** @@ -45,11 +42,11 @@ class MarkerBasedTemplateServiceTest extends UnitTestCase protected function setUp(): void { parent::setUp(); - $cacheManagerProphecy = $this->prophesize(CacheManager::class); - GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal()); $cacheFrontendProphecy = $this->prophesize(FrontendInterface::class); - $cacheManagerProphecy->getCache(Argument::cetera())->willReturn($cacheFrontendProphecy->reveal()); - $this->templateService = new MarkerBasedTemplateService(); + $this->templateService = new MarkerBasedTemplateService( + $cacheFrontendProphecy->reveal(), + $cacheFrontendProphecy->reveal(), + ); } /** diff --git a/typo3/sysext/frontend/Classes/ContentObject/ImageContentObject.php b/typo3/sysext/frontend/Classes/ContentObject/ImageContentObject.php index 0b9b2c54e17e..b4ee37ccef2a 100644 --- a/typo3/sysext/frontend/Classes/ContentObject/ImageContentObject.php +++ b/typo3/sysext/frontend/Classes/ContentObject/ImageContentObject.php @@ -27,6 +27,11 @@ use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; */ class ImageContentObject extends AbstractContentObject { + public function __construct( + protected readonly MarkerBasedTemplateService $markerTemplateService, + ) { + } + /** * Rendering the cObject, IMAGE * @@ -96,8 +101,7 @@ class ImageContentObject extends AbstractContentObject 'selfClosingTagSlash' => !empty($tsfe->xhtmlDoctype) ? ' /' : '', ]; - $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class); - $theValue = $markerTemplateEngine->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true); + $theValue = $this->markerTemplateService->substituteMarkerArray($imageTagTemplate, $imageTagValues, '###|###', true, true); $linkWrap = (string)$this->cObj->stdWrapValue('linkWrap', $conf ?? []); if ($linkWrap !== '') { @@ -243,8 +247,7 @@ class ImageContentObject extends AbstractContentObject $sourceConfiguration['src'] = htmlspecialchars($urlPrefix . $sourceInfo[3]); $sourceConfiguration['selfClosingTagSlash'] = !empty($tsfe->xhtmlDoctype) ? ' /' : ''; - $markerTemplateEngine = GeneralUtility::makeInstance(MarkerBasedTemplateService::class); - $oneSourceCollection = $markerTemplateEngine->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true); + $oneSourceCollection = $this->markerTemplateService->substituteMarkerArray($sourceLayout, $sourceConfiguration, '###|###', true, true); foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_content.php']['getImageSourceCollection'] ?? [] as $className) { $hookObject = GeneralUtility::makeInstance($className); diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/FluidTemplateContentObjectTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/FluidTemplateContentObjectTest.php index 5d2464d9fc57..8af65248ea59 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/FluidTemplateContentObjectTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/FluidTemplateContentObjectTest.php @@ -27,6 +27,7 @@ use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Page\ImportMap; use TYPO3\CMS\Core\Page\ImportMapFactory; use TYPO3\CMS\Core\Page\PageRenderer; +use TYPO3\CMS\Core\Tests\Unit\Page\PageRendererFactoryTrait; use TYPO3\CMS\Core\TypoScript\TemplateService; use TYPO3\CMS\Core\TypoScript\TypoScriptService; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -49,6 +50,7 @@ use TYPO3Fluid\Fluid\View\AbstractTemplateView; class FluidTemplateContentObjectTest extends UnitTestCase { use ProphecyTrait; + use PageRendererFactoryTrait; /** * @var bool Reset singletons created by subject @@ -135,6 +137,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase */ public function renderCallsInitializeStandaloneViewInstance(): void { + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $this->addMockViewToSubject(); $this->subject->expects(self::once())->method('initializeStandaloneViewInstance'); $this->subject->render([]); @@ -158,6 +164,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $standAloneView = $this->prophesize(StandaloneView::class); @@ -183,6 +193,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase { $configuration = ['file' => 'EXT:core/bar.html']; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('file', $configuration)->willReturn('EXT:core/bar.html'); @@ -212,6 +226,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(7); $contentObjectRenderer->cObjGetSingle('FILE', ['file' => Environment::getPublicPath() . '/foo/bar.html'], 'template')->willReturn('baz'); @@ -241,6 +259,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase 1 => 'dummyPath2/', ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('templateName', $configuration)->willReturn('foo'); @@ -275,6 +297,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('templateName', $configuration)->willReturn('bar'); @@ -301,6 +327,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase { $configuration = ['layoutRootPath' => 'foo/bar.html']; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('layoutRootPath', $configuration)->willReturn('foo/bar.html'); @@ -331,6 +361,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); @@ -363,6 +397,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrap('FILE', ['file' => 'foo/bar.html'])->shouldBeCalled(); @@ -387,6 +425,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase */ public function fallbacksForLayoutRootPathAreSet(): void { + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $this->addMockViewToSubject(); $this->standaloneView ->expects(self::once()) @@ -408,6 +450,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase 'layoutRootPaths.' => [10 => 'foo/bar.html', 20 => 'foo/bar2.html'], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('layoutRootPath', $configuration)->willReturn('foo/main.html'); @@ -437,6 +483,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase { $configuration = ['partialRootPath' => 'foo/bar.html']; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('partialRootPath', $configuration)->willReturn('foo/bar.html'); @@ -470,6 +520,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $standAloneView = $this->prophesize(StandaloneView::class); @@ -497,6 +551,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase 'partialRootPath.' => ['bar' => 'baz'], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $subject = new FluidTemplateContentObject($this->contentDataProcessor); $subject->setContentObjectRenderer($contentObjectRenderer->reveal()); @@ -519,6 +577,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase */ public function fallbacksForPartialRootPathAreSet(): void { + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $this->addMockViewToSubject(); $this->standaloneView ->expects(self::once()) @@ -537,6 +599,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase 'partialRootPaths.' => [10 => 'foo', 20 => 'bar'], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('partialRootPath', $configuration)->willReturn(Environment::getPublicPath() . '/main'); @@ -570,6 +636,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase 'format' => 'xml', ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('format', $configuration)->willReturn('xml'); @@ -598,6 +668,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase 'format.' => ['bar' => 'baz'], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $subject = new FluidTemplateContentObject($this->contentDataProcessor); @@ -625,6 +699,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('pluginName', ['pluginName' => 'foo'])->willReturn('foo'); @@ -659,6 +737,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $subject = new FluidTemplateContentObject($this->contentDataProcessor); @@ -686,6 +768,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('controllerExtensionName', ['controllerExtensionName' => 'foo'])->willReturn('foo'); @@ -720,6 +806,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $subject = new FluidTemplateContentObject($this->contentDataProcessor); @@ -747,6 +837,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('controllerName', ['controllerName' => 'foo'])->willReturn('foo'); @@ -781,6 +875,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $subject = new FluidTemplateContentObject($this->contentDataProcessor); @@ -808,6 +906,10 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $contentObjectRenderer->stdWrapValue(Argument::cetera())->shouldBeCalledTimes(8); $contentObjectRenderer->stdWrapValue('controllerActionName', ['controllerActionName' => 'foo'])->willReturn('foo'); @@ -841,6 +943,11 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); + $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $subject = new FluidTemplateContentObject($this->contentDataProcessor); @@ -878,6 +985,11 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); + /** @var TypoScriptService|MockObject $typoScriptServiceMock */ $typoScriptServiceMock = $this->getMockBuilder(TypoScriptService::class)->getMock(); $typoScriptServiceMock @@ -944,6 +1056,11 @@ class FluidTemplateContentObjectTest extends UnitTestCase */ public function renderCallsCObjGetSingleForAllowedVariable(): void { + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); + $configuration = [ 'variables.' => [ 'aVar' => 'TEXT', @@ -974,6 +1091,11 @@ class FluidTemplateContentObjectTest extends UnitTestCase */ public function renderAssignsRenderedContentObjectVariableToView(): void { + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); + $configuration = [ 'variables.' => [ 'aVar' => 'TEXT', @@ -1008,6 +1130,11 @@ class FluidTemplateContentObjectTest extends UnitTestCase */ public function renderAssignsContentObjectRendererDataToView(): void { + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); + $this->addMockViewToSubject(); $this->contentObjectRenderer->data = ['foo']; $this->standaloneView @@ -1022,6 +1149,11 @@ class FluidTemplateContentObjectTest extends UnitTestCase */ public function renderAssignsContentObjectRendererCurrentValueToView(): void { + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); + $this->addMockViewToSubject(); $this->contentObjectRenderer->data = ['currentKey' => 'currentValue']; $this->contentObjectRenderer->currentValKey = 'currentKey'; @@ -1037,6 +1169,11 @@ class FluidTemplateContentObjectTest extends UnitTestCase */ public function renderCallsRenderOnStandaloneView(): void { + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); + $this->addMockViewToSubject(); $this->standaloneView ->expects(self::once()) @@ -1055,6 +1192,11 @@ class FluidTemplateContentObjectTest extends UnitTestCase ], ]; + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); + $contentObjectRenderer = $this->prophesize(ContentObjectRenderer::class); $subject = new FluidTemplateContentObject($this->contentDataProcessor); @@ -1084,10 +1226,13 @@ class FluidTemplateContentObjectTest extends UnitTestCase ?string $expectedHeader, ?string $expectedFooter ): void { - $pageRendererMock = $this->getMockBuilder(PageRenderer::class)->onlyMethods([ - 'addHeaderData', - 'addFooterData', - ])->getMock(); + $pageRendererMock = $this->getMockBuilder(PageRenderer::class) + ->setConstructorArgs($this->getPageRendererConstructorArgs()) + ->onlyMethods([ + 'addHeaderData', + 'addFooterData', + ]) + ->getMock(); if ($expectedHeader !== null && !empty(trim($expectedHeader))) { $pageRendererMock->expects(self::once())->method('addHeaderData')->with($expectedHeader); } else { diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/ImageContentObjectTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/ImageContentObjectTest.php index d9565300e9aa..cb5e4bd40d45 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/ImageContentObjectTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/ImageContentObjectTest.php @@ -19,7 +19,9 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject; use PHPUnit\Framework\MockObject\MockObject; use Prophecy\PhpUnit\ProphecyTrait; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; use TYPO3\CMS\Core\Resource\ResourceFactory; +use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\StringUtility; use TYPO3\CMS\Frontend\ContentObject\ContentObjectOneSourceCollectionHookInterface; @@ -52,7 +54,12 @@ class ImageContentObjectTest extends UnitTestCase $tsfe = $this->prophesize(TypoScriptFrontendController::class); $GLOBALS['TSFE'] = $tsfe->reveal(); $contentObjectRenderer = new ContentObjectRenderer($tsfe->reveal()); - $this->subject = $this->getAccessibleMock(ImageContentObject::class, ['dummy']); + $this->subject = $this->getAccessibleMock(ImageContentObject::class, ['dummy'], [ + new MarkerBasedTemplateService( + new NullFrontend('hash'), + new NullFrontend('runtime'), + ), + ]); $this->subject->setContentObjectRenderer($contentObjectRenderer); } @@ -80,8 +87,7 @@ class ImageContentObjectTest extends UnitTestCase public function getImageTagTemplateFallsBackToDefaultTemplateIfNoTemplateIsFound($key, $configuration): void { $defaultImgTagTemplate = '<img src="###SRC###" width="###WIDTH###" height="###HEIGHT###" ###PARAMS### ###ALTPARAMS### ###BORDER######SELFCLOSINGTAGSLASH###>'; - $subject = $this->getAccessibleMock(ImageContentObject::class, ['dummy'], [], '', false); - $result = $subject->_call('getImageTagTemplate', $key, $configuration); + $result = $this->subject->_call('getImageTagTemplate', $key, $configuration); self::assertEquals($result, $defaultImgTagTemplate); } @@ -195,9 +201,8 @@ class ImageContentObjectTest extends UnitTestCase ->with(self::equalTo('testImageName')) ->willReturn([100, 100, null, 'bar']); - $subject = $this->getAccessibleMock(ImageContentObject::class, ['dummy']); - $subject->setContentObjectRenderer($cObj); - $result = $subject->_call('getImageSourceCollection', $layoutKey, $configuration, $file); + $this->subject->setContentObjectRenderer($cObj); + $result = $this->subject->_call('getImageSourceCollection', $layoutKey, $configuration, $file); self::assertEquals('---bar---', $result); } @@ -266,9 +271,8 @@ class ImageContentObjectTest extends UnitTestCase ->method('stdWrap') ->willReturnArgument(0); - $subject = $this->getAccessibleMock(ImageContentObject::class, ['dummy']); - $subject->setContentObjectRenderer($cObj); - $result = $subject->_call('getImageSourceCollection', $layoutKey, $configuration, $file); + $this->subject->setContentObjectRenderer($cObj); + $result = $this->subject->_call('getImageSourceCollection', $layoutKey, $configuration, $file); self::assertEmpty($result); } @@ -399,7 +403,12 @@ class ImageContentObjectTest extends UnitTestCase ->with(self::equalTo('testImageName')) ->willReturn([100, 100, null, 'bar-file.jpg']); - $subject = $this->getAccessibleMock(ImageContentObject::class, ['dummy']); + $subject = $this->getAccessibleMock(ImageContentObject::class, ['dummy'], [ + new MarkerBasedTemplateService( + new NullFrontend('hash'), + new NullFrontend('runtime'), + ), + ]); $subject->setContentObjectRenderer($cObj); $result = $subject->_call('getImageSourceCollection', $layoutKey, $configuration, $file); @@ -464,9 +473,8 @@ class ImageContentObjectTest extends UnitTestCase ], ]; - $subject = $this->getAccessibleMock(ImageContentObject::class, ['dummy']); - $subject->setContentObjectRenderer($cObj); - $result = $subject->_call('getImageSourceCollection', 'data', $configuration, StringUtility::getUniqueId('testImage-')); + $this->subject->setContentObjectRenderer($cObj); + $result = $this->subject->_call('getImageSourceCollection', 'data', $configuration, StringUtility::getUniqueId('testImage-')); self::assertSame($result, 'isGetOneSourceCollectionCalledCallback'); } diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php index 21171b609a93..ea2d942ec726 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php @@ -40,8 +40,10 @@ use TYPO3\CMS\Core\Localization\LocalizationFactory; use TYPO3\CMS\Core\Package\PackageManager; use TYPO3\CMS\Core\Page\ImportMap; use TYPO3\CMS\Core\Page\ImportMapFactory; +use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Routing\PageArguments; use TYPO3\CMS\Core\Site\Entity\Site; +use TYPO3\CMS\Core\Tests\Unit\Page\PageRendererFactoryTrait; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; @@ -53,6 +55,7 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class AbstractMenuContentObjectTest extends UnitTestCase { use ProphecyTrait; + use PageRendererFactoryTrait; /** * @var AbstractMenuContentObject|MockObject|AccessibleObjectInterface @@ -95,6 +98,10 @@ class AbstractMenuContentObjectTest extends UnitTestCase $importMapFactoryProphecy = $this->prophesize(ImportMapFactory::class); $importMapFactoryProphecy->create()->willReturn($importMapProphecy->reveal()); GeneralUtility::setSingletonInstance(ImportMapFactory::class, $importMapFactoryProphecy->reveal()); + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $frontendUserProphecy = $this->prophesize(FrontendUserAuthentication::class); $GLOBALS['TSFE'] = $this->getMockBuilder(TypoScriptFrontendController::class) ->setConstructorArgs([new Context(), $site, $site->getDefaultLanguage(), new PageArguments(1, '1', []), $frontendUserProphecy->reveal()]) diff --git a/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php b/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php index b8285caa4bed..4ac4bd94cfd6 100644 --- a/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php +++ b/typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php @@ -44,6 +44,7 @@ use TYPO3\CMS\Core\PageTitle\PageTitleProviderManager; use TYPO3\CMS\Core\Routing\PageArguments; use TYPO3\CMS\Core\Site\Entity\Site; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; +use TYPO3\CMS\Core\Tests\Unit\Page\PageRendererFactoryTrait; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\StringUtility; use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication; @@ -58,6 +59,7 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class TypoScriptFrontendControllerTest extends UnitTestCase { use ProphecyTrait; + use PageRendererFactoryTrait; /** * @var bool Reset singletons created by subject @@ -77,6 +79,10 @@ class TypoScriptFrontendControllerTest extends UnitTestCase $importMapFactoryProphecy = $this->prophesize(ImportMapFactory::class); $importMapFactoryProphecy->create()->willReturn($importMapProphecy->reveal()); GeneralUtility::setSingletonInstance(ImportMapFactory::class, $importMapFactoryProphecy->reveal()); + GeneralUtility::setSingletonInstance( + PageRenderer::class, + new PageRenderer(...$this->getPageRendererConstructorArgs()), + ); $site = $this->createSiteWithDefaultLanguage([ 'locale' => 'fr', 'typo3Language' => 'fr', @@ -90,7 +96,9 @@ class TypoScriptFrontendControllerTest extends UnitTestCase $pageRepository = $this->getMockBuilder(PageRepository::class)->getMock(); $this->subject->sys_page = $pageRepository; - $pageRenderer = $this->getMockBuilder(PageRenderer::class)->getMock(); + $pageRenderer = $this->getMockBuilder(PageRenderer::class) + ->setConstructorArgs($this->getPageRendererConstructorArgs()) + ->getMock(); $this->subject->_set('pageRenderer', $pageRenderer); } diff --git a/typo3/sysext/frontend/Tests/Unit/Plugin/AbstractPluginTest.php b/typo3/sysext/frontend/Tests/Unit/Plugin/AbstractPluginTest.php index fa3ff9b4c8e3..1d3aa354fa52 100644 --- a/typo3/sysext/frontend/Tests/Unit/Plugin/AbstractPluginTest.php +++ b/typo3/sysext/frontend/Tests/Unit/Plugin/AbstractPluginTest.php @@ -21,6 +21,8 @@ use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\DependencyInjection\Container; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; +use TYPO3\CMS\Core\Service\MarkerBasedTemplateService; use TYPO3\CMS\Core\Site\Entity\Site; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\StringUtility; @@ -62,6 +64,10 @@ class AbstractPluginTest extends UnitTestCase return $args[0] ?? ''; }); + GeneralUtility::addInstance(MarkerBasedTemplateService::class, new MarkerBasedTemplateService( + new NullFrontend('hash'), + new NullFrontend('runtime'), + )); $this->abstractPlugin = new AbstractPlugin(null, $tsfe->reveal()); $contentObjectRenderer = new ContentObjectRenderer($tsfe->reveal()); $contentObjectRenderer->setRequest($this->prophesize(ServerRequestInterface::class)->reveal()); -- GitLab