From 6c61b07131d7942ac6d9a3eb4ff817135ef9e985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Bajsarowicz?= Date: Mon, 13 Apr 2026 23:28:53 +0200 Subject: [PATCH] perf: cache resolved theme in Theme Resolver Avoid redundant theme collection loading on repeated get() calls within the same request. Ref: magento/magento2#40721 --- .../Magento/Theme/Model/Theme/Resolver.php | 37 ++++-- .../Test/Unit/Model/Theme/ResolverTest.php | 120 ++++++++++++++++++ 2 files changed, 148 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Theme/Model/Theme/Resolver.php b/app/code/Magento/Theme/Model/Theme/Resolver.php index 2d52dc19b00f0..bc7a87c60459c 100644 --- a/app/code/Magento/Theme/Model/Theme/Resolver.php +++ b/app/code/Magento/Theme/Model/Theme/Resolver.php @@ -10,6 +10,16 @@ */ class Resolver implements \Magento\Framework\View\Design\Theme\ResolverInterface { + /** + * @var array + */ + private $resolvedThemes = []; + + /** + * @var bool[] + */ + private $isThemeResolved = []; + /** * @var \Magento\Framework\View\DesignInterface */ @@ -48,19 +58,28 @@ public function __construct( public function get() { $area = $this->appState->getAreaCode(); - if ($this->design->getDesignTheme()->getArea() == $area || $this->design->getArea() == $area) { - return $this->design->getDesignTheme(); + if ($this->isThemeResolved[$area] ?? false) { + return $this->resolvedThemes[$area]; } - /** @var \Magento\Theme\Model\ResourceModel\Theme\Collection $themeCollection */ - $themeCollection = $this->themeFactory->create(); - $themeIdentifier = $this->design->getConfigurationDesignTheme($area); - if (is_numeric($themeIdentifier)) { - $result = $themeCollection->getItemById($themeIdentifier); + $designTheme = $this->design->getDesignTheme(); + if (($designTheme && $designTheme->getArea() == $area) || $this->design->getArea() == $area) { + $result = $designTheme; } else { - $themeFullPath = $area . \Magento\Framework\View\Design\ThemeInterface::PATH_SEPARATOR . $themeIdentifier; - $result = $themeCollection->getThemeByFullPath($themeFullPath); + /** @var \Magento\Theme\Model\ResourceModel\Theme\Collection $themeCollection */ + $themeCollection = $this->themeFactory->create(); + $themeIdentifier = $this->design->getConfigurationDesignTheme($area); + if (is_numeric($themeIdentifier)) { + $result = $themeCollection->getItemById($themeIdentifier); + } else { + $themeFullPath = $area . \Magento\Framework\View\Design\ThemeInterface::PATH_SEPARATOR . $themeIdentifier; + $result = $themeCollection->getThemeByFullPath($themeFullPath); + } } + + $this->resolvedThemes[$area] = $result; + $this->isThemeResolved[$area] = true; + return $result; } } diff --git a/app/code/Magento/Theme/Test/Unit/Model/Theme/ResolverTest.php b/app/code/Magento/Theme/Test/Unit/Model/Theme/ResolverTest.php index 78caf14e9b046..b436fe3a5c40b 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/Theme/ResolverTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/Theme/ResolverTest.php @@ -258,4 +258,124 @@ public function testGetByAreaWithOtherAreaAndNumericThemeId() $this->assertEquals($this->themeMock, $this->model->get()); } + + public function testGetCachesResolvedThemeByArea() + { + $this->designMock->expects( + $this->once() + )->method( + 'getDesignTheme' + )->willReturn( + $this->themeMock + ); + $this->designMock->expects( + $this->once() + )->method( + 'getArea' + )->willReturn( + 'design_area' + ); + $this->designMock->expects( + $this->once() + )->method( + 'getConfigurationDesignTheme' + )->with( + 'other_area' + )->willReturn( + 'other_theme' + ); + + $this->themeMock->expects( + $this->once() + )->method( + 'getArea' + )->willReturn( + 'theme_area' + ); + + $this->themeCollectionFactoryMock->expects( + $this->once() + )->method( + 'create' + )->willReturn( + $this->themeCollectionMock + ); + + $this->themeCollectionMock->expects( + $this->once() + )->method( + 'getThemeByFullPath' + )->with( + 'other_area' . ThemeInterface::PATH_SEPARATOR . 'other_theme' + )->willReturn( + $this->themeMock + ); + + $this->appStateMock->expects( + $this->exactly(2) + )->method( + 'getAreaCode' + )->willReturn( + 'other_area' + ); + + $this->assertSame($this->themeMock, $this->model->get()); + $this->assertSame($this->themeMock, $this->model->get()); + } + + public function testGetCachesNullResolvedThemeByArea() + { + $this->designMock->expects( + $this->once() + )->method( + 'getDesignTheme' + )->willReturn( + null + ); + $this->designMock->expects( + $this->once() + )->method( + 'getArea' + )->willReturn( + 'design_area' + ); + $this->designMock->expects( + $this->once() + )->method( + 'getConfigurationDesignTheme' + )->with( + 'other_area' + )->willReturn( + 12 + ); + + $this->themeCollectionFactoryMock->expects( + $this->once() + )->method( + 'create' + )->willReturn( + $this->themeCollectionMock + ); + + $this->themeCollectionMock->expects( + $this->once() + )->method( + 'getItemById' + )->with( + 12 + )->willReturn( + null + ); + + $this->appStateMock->expects( + $this->exactly(2) + )->method( + 'getAreaCode' + )->willReturn( + 'other_area' + ); + + $this->assertNull($this->model->get()); + $this->assertNull($this->model->get()); + } }