Skip to content

Commit cdf3a00

Browse files
committed
OXDEV-9981 Cache template chains
1 parent 1dfc554 commit cdf3a00

7 files changed

Lines changed: 273 additions & 230 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/**
4+
* Copyright © OXID eSales AG. All rights reserved.
5+
* See LICENSE file for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OxidEsales\Twig\Event;
11+
12+
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\Event\ModuleConfigurationChangedEvent;
13+
use OxidEsales\EshopCommunity\Internal\Framework\Module\Setup\Event\FinalizingModuleActivationEvent;
14+
use OxidEsales\EshopCommunity\Internal\Framework\Module\Setup\Event\FinalizingModuleDeactivationEvent;
15+
use OxidEsales\EshopCommunity\Internal\Framework\Module\Setup\Event\ModuleSetupEvent;
16+
use OxidEsales\EshopCommunity\Internal\Framework\Theme\Event\ThemeSettingChangedEvent;
17+
use OxidEsales\EshopCommunity\Internal\Framework\Theme\Event\ThemeActivatedEvent;
18+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
19+
use Symfony\Contracts\Cache\TagAwareCacheInterface;
20+
21+
final class InvalidateTemplateChainCacheEventSubscriber implements EventSubscriberInterface
22+
{
23+
public function __construct(private readonly TagAwareCacheInterface $cache)
24+
{
25+
}
26+
27+
public function invalidateTemplateChainCache(
28+
ModuleSetupEvent|ModuleConfigurationChangedEvent|ThemeSettingChangedEvent|ThemeActivatedEvent $event
29+
): void {
30+
$this->cache->invalidateTags(['oxid_esales.cache.twig.template_chain']);
31+
}
32+
33+
public static function getSubscribedEvents(): array
34+
{
35+
return [
36+
FinalizingModuleActivationEvent::class => 'invalidateTemplateChainCache',
37+
FinalizingModuleDeactivationEvent::class => 'invalidateTemplateChainCache',
38+
ModuleConfigurationChangedEvent::class => 'invalidateTemplateChainCache',
39+
ThemeSettingChangedEvent::class => 'invalidateTemplateChainCache',
40+
ThemeActivatedEvent::class => 'invalidateTemplateChainCache',
41+
];
42+
}
43+
}

src/Event/services.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ services:
88
- '@OxidEsales\Twig\Resolver\TemplateDirectoryResolverInterface'
99
tags:
1010
- { name: kernel.event_subscriber }
11+
12+
OxidEsales\Twig\Event\InvalidateTemplateChainCacheEventSubscriber:
13+
tags:
14+
- { name: kernel.event_subscriber }

src/Resolver/TemplateChain/TemplateChainResolver.php

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,50 +9,60 @@
99

1010
namespace OxidEsales\Twig\Resolver\TemplateChain;
1111

12+
use OxidEsales\Twig\Resolver\TemplateChain\DataObject\TemplateChain;
1213
use OxidEsales\Twig\Resolver\TemplateChain\TemplateType\TemplateTypeFactoryInterface;
14+
use Symfony\Contracts\Cache\ItemInterface;
15+
use Symfony\Contracts\Cache\TagAwareCacheInterface;
1316

1417
class TemplateChainResolver implements TemplateChainResolverInterface
1518
{
16-
private array $lastChildCache = [];
17-
private array $parentCache = [];
18-
1919
public function __construct(
2020
private TemplateChainBuilderInterface $templateChainBuilder,
2121
private TemplateTypeFactoryInterface $templateTypeFactory,
22+
private TagAwareCacheInterface $cache,
2223
) {
2324
}
2425

2526
public function getParent(string $templateName): string
2627
{
27-
if (!isset($this->parentCache[$templateName])) {
28-
$templateType = $this->templateTypeFactory->createFromTemplateName($templateName);
29-
$this->parentCache[$templateName] = $this->templateChainBuilder
30-
->getChain($templateType)
31-
->getParent($templateType)
32-
->getFullyQualifiedName();
33-
}
34-
35-
return $this->parentCache[$templateName];
28+
$templateType = $this->templateTypeFactory->createFromTemplateName($templateName);
29+
30+
return $this->getTemplateChain($templateName)->getParent($templateType)->getFullyQualifiedName();
3631
}
3732

3833
public function getLastChild(string $templateName): string
3934
{
40-
if (!isset($this->lastChildCache[$templateName])) {
41-
$templateType = $this->templateTypeFactory->createFromTemplateName($templateName);
42-
$this->lastChildCache[$templateName] = $this->templateChainBuilder
43-
->getChain($templateType)
44-
->getLastChild()
45-
->getFullyQualifiedName();
46-
}
47-
48-
return $this->lastChildCache[$templateName];
35+
return $this->getTemplateChain($templateName)->getLastChild()->getFullyQualifiedName();
4936
}
5037

5138
public function hasParent(string $templateName): bool
5239
{
5340
$templateType = $this->templateTypeFactory->createFromTemplateName($templateName);
54-
return $this->templateChainBuilder
55-
->getChain($templateType)
56-
->hasParent($templateType);
41+
42+
return $this->getTemplateChain($templateName)->hasParent($templateType);
43+
}
44+
45+
private function getTemplateChain(string $templateName): TemplateChain
46+
{
47+
return $this->cache->get(
48+
$this->getCacheKey($templateName),
49+
function (ItemInterface $item) use ($templateName): TemplateChain {
50+
$item->tag('oxid_esales.cache.twig.template_chain');
51+
52+
return $this->createTemplateChain($templateName);
53+
}
54+
);
55+
}
56+
57+
private function createTemplateChain(string $templateName): TemplateChain
58+
{
59+
$templateType = $this->templateTypeFactory->createFromTemplateName($templateName);
60+
61+
return $this->templateChainBuilder->getChain($templateType);
62+
}
63+
64+
private function getCacheKey(string $templateName): string
65+
{
66+
return 'twig_template_chain_' . sha1($templateName);
5767
}
5868
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/**
4+
* Copyright © OXID eSales AG. All rights reserved.
5+
* See LICENSE file for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OxidEsales\Twig\Tests\Integration\Event;
11+
12+
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\DataObject\ModuleConfiguration;
13+
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\Event\ModuleConfigurationChangedEvent;
14+
use OxidEsales\EshopCommunity\Internal\Framework\Theme\Event\ThemeSettingChangedEvent;
15+
use OxidEsales\EshopCommunity\Internal\Framework\Theme\Event\ThemeActivatedEvent;
16+
use OxidEsales\EshopCommunity\Internal\Transition\Utility\BasicContextInterface;
17+
use OxidEsales\EshopCommunity\Tests\Integration\IntegrationTestCase;
18+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
19+
use Symfony\Contracts\Cache\TagAwareCacheInterface;
20+
21+
final class InvalidateTemplateChainCacheEventSubscriberTest extends IntegrationTestCase
22+
{
23+
private const CACHE_KEY = 'test_template_chain_cache_entry';
24+
25+
private EventDispatcherInterface $eventDispatcher;
26+
private TagAwareCacheInterface $cache;
27+
private int $shopId;
28+
29+
public function setUp(): void
30+
{
31+
parent::setUp();
32+
33+
$this->eventDispatcher = $this->get(EventDispatcherInterface::class);
34+
$this->shopId = $this->get(BasicContextInterface::class)->getDefaultShopId();
35+
$this->cache = $this->get(TagAwareCacheInterface::class);
36+
$this->cache->invalidateTags(['oxid_esales.cache.twig.template_chain']);
37+
}
38+
39+
public function testModuleConfigurationChangedEventInvalidatesTemplateChainCache(): void
40+
{
41+
$this->putTaggedCacheEntry();
42+
43+
$moduleConfiguration = (new ModuleConfiguration())
44+
->setId('test_module')
45+
->setModuleSource('source/modules/test_module');
46+
47+
$this->eventDispatcher->dispatch(
48+
new ModuleConfigurationChangedEvent($moduleConfiguration, $this->shopId)
49+
);
50+
51+
$this->assertFalse($this->cache->getItem(self::CACHE_KEY)->isHit());
52+
}
53+
54+
public function testThemeSettingChangedEventInvalidatesTemplateChainCache(): void
55+
{
56+
$this->putTaggedCacheEntry();
57+
58+
$this->eventDispatcher->dispatch(
59+
new ThemeSettingChangedEvent('someThemeSetting', $this->shopId, 'theme:apex')
60+
);
61+
62+
$this->assertFalse($this->cache->getItem(self::CACHE_KEY)->isHit());
63+
}
64+
65+
public function testThemeActivatedEventInvalidatesTemplateChainCache(): void
66+
{
67+
$this->putTaggedCacheEntry();
68+
69+
$this->eventDispatcher->dispatch(
70+
new ThemeActivatedEvent($this->shopId, 'test-theme')
71+
);
72+
73+
$this->assertFalse($this->cache->getItem(self::CACHE_KEY)->isHit());
74+
}
75+
76+
private function putTaggedCacheEntry(): void
77+
{
78+
$item = $this->cache->getItem(self::CACHE_KEY);
79+
$item->set('cached_value');
80+
$item->tag(['oxid_esales.cache.twig.template_chain']);
81+
82+
$this->cache->save($item);
83+
}
84+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/**
4+
* Copyright © OXID eSales AG. All rights reserved.
5+
* See LICENSE file for license details.
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OxidEsales\Twig\Tests\Integration\TwigEngine\TemplateChain;
11+
12+
use OxidEsales\EshopCommunity\Internal\Framework\Cache\Adapter\TagAwareAdapterFactoryInterface;
13+
use OxidEsales\EshopCommunity\Internal\Transition\Utility\BasicContextInterface;
14+
use OxidEsales\EshopCommunity\Tests\Integration\IntegrationTestCase;
15+
use OxidEsales\Twig\Resolver\TemplateChain\DataObject\TemplateChain;
16+
use OxidEsales\Twig\Resolver\TemplateChain\TemplateChainResolverInterface;
17+
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
18+
19+
final class TemplateChainResolverCacheTest extends IntegrationTestCase
20+
{
21+
private const TEMPLATE_NAME = 'layout/base.html.twig';
22+
23+
private TemplateChainResolverInterface $resolver;
24+
private TagAwareAdapterInterface $cache;
25+
26+
public function setUp(): void
27+
{
28+
parent::setUp();
29+
30+
$this->resolver = $this->get(TemplateChainResolverInterface::class);
31+
$this->cache = $this->get(TagAwareAdapterFactoryInterface::class)->create(
32+
$this->get(BasicContextInterface::class)->getDefaultShopId()
33+
);
34+
$this->cache->invalidateTags(['oxid_esales.cache.twig.template_chain']);
35+
}
36+
37+
public function testGetLastChildPopulatesCacheOnFirstCall(): void
38+
{
39+
$cacheKey = $this->getCacheKey(self::TEMPLATE_NAME);
40+
41+
$this->assertFalse($this->cache->getItem($cacheKey)->isHit());
42+
43+
$this->resolver->getLastChild(self::TEMPLATE_NAME);
44+
45+
$this->assertTrue($this->cache->getItem($cacheKey)->isHit());
46+
}
47+
48+
private function getCacheKey(string $templateName): string
49+
{
50+
return 'twig_template_chain_' . sha1($templateName);
51+
}
52+
}

0 commit comments

Comments
 (0)