Skip to content

Commit 1aaedd4

Browse files
committed
Obscure host template filename in public facing attribute
1 parent 69aefec commit 1aaedd4

File tree

9 files changed

+135
-7
lines changed

9 files changed

+135
-7
lines changed

src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
use Symfony\UX\LiveComponent\Twig\DeterministicTwigIdCalculator;
3838
use Symfony\UX\LiveComponent\Twig\LiveComponentExtension as LiveComponentTwigExtension;
3939
use Symfony\UX\LiveComponent\Twig\LiveComponentRuntime;
40+
use Symfony\UX\LiveComponent\Twig\TemplateCacheWarmer;
41+
use Symfony\UX\LiveComponent\Twig\TemplateMap;
4042
use Symfony\UX\LiveComponent\Util\ChildComponentPartialRenderer;
4143
use Symfony\UX\LiveComponent\Util\FingerprintCalculator;
4244
use Symfony\UX\LiveComponent\Util\LiveControllerAttributesCreator;
@@ -55,6 +57,8 @@
5557
*/
5658
final class LiveComponentExtension extends Extension implements PrependExtensionInterface
5759
{
60+
public const TEMPLATES_MAP_FILENAME = 'live_components_twig_templates.map';
61+
5862
public function prepend(ContainerBuilder $container)
5963
{
6064
// Register the form theme if TwigBundle is available
@@ -190,12 +194,14 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
190194
new Reference('router'),
191195
new Reference('ux.live_component.live_responder'),
192196
new Reference('security.csrf.token_manager', ContainerInterface::NULL_ON_INVALID_REFERENCE),
197+
new Reference('ux.live_component.twig.template_mapper'),
193198
])
194199
;
195200

196201
$container->register('ux.live_component.add_attributes_subscriber', AddLiveAttributesSubscriber::class)
197202
->setArguments([
198203
new Reference('ux.twig_component.component_stack'),
204+
new Reference('ux.live_component.twig.template_mapper'),
199205
])
200206
->addTag('kernel.event_subscriber')
201207
->addTag('container.service_subscriber', ['key' => LiveControllerAttributesCreator::class, 'id' => 'ux.live_component.live_controller_attributes_creator'])
@@ -212,6 +218,13 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
212218
->addTag('form.type')
213219
->setPublic(false)
214220
;
221+
222+
$container->register('ux.live_component.twig.template_mapper', TemplateMap::class)
223+
->setArguments(['%kernel.cache_dir%/'.self::TEMPLATES_MAP_FILENAME]);
224+
225+
$container->register('ux.live_component.twig.cache_warmer', TemplateCacheWarmer::class)
226+
->setArguments([new Reference('twig.template_iterator'), self::TEMPLATES_MAP_FILENAME])
227+
->addTag('kernel.cache_warmer');
215228
}
216229

217230
private function isAssetMapperAvailable(ContainerBuilder $container): bool

src/LiveComponent/src/EventListener/AddLiveAttributesSubscriber.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Container\ContainerInterface;
1515
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1616
use Symfony\Contracts\Service\ServiceSubscriberInterface;
17+
use Symfony\UX\LiveComponent\Twig\TemplateMap;
1718
use Symfony\UX\LiveComponent\Util\LiveControllerAttributesCreator;
1819
use Symfony\UX\TwigComponent\ComponentAttributes;
1920
use Symfony\UX\TwigComponent\ComponentMetadata;
@@ -36,7 +37,8 @@ final class AddLiveAttributesSubscriber implements EventSubscriberInterface, Ser
3637
{
3738
public function __construct(
3839
private ComponentStack $componentStack,
39-
private ContainerInterface $container
40+
private TemplateMap $templateMap,
41+
private ContainerInterface $container,
4042
) {
4143
}
4244

@@ -67,7 +69,7 @@ public function onPreRender(PreRenderEvent $event): void
6769
// the embedded template so that the blocks from that template
6870
// will be used, if any, instead of the originals.
6971
$event->setTemplate(
70-
$originalAttributes['data-host-template'],
72+
$this->templateMap->resolve($originalAttributes['data-host-template']),
7173
$originalAttributes['data-embedded-template-index'],
7274
);
7375
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\UX\LiveComponent\Twig;
6+
7+
use Symfony\Component\Cache\Adapter\NullAdapter;
8+
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
9+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
10+
11+
/**
12+
* @author Bart Vanderstukken <bart.vanderstukken@gmail.com>
13+
*/
14+
final class TemplateCacheWarmer implements CacheWarmerInterface
15+
{
16+
public function __construct(private \IteratorAggregate $templateIterator, private readonly string $cacheFilename)
17+
{
18+
}
19+
20+
public function warmUp(string $cacheDir): void
21+
{
22+
$map = [];
23+
foreach ($this->templateIterator as $item) {
24+
$map[bin2hex(random_bytes(16))] = $item;
25+
}
26+
27+
(new PhpArrayAdapter($cacheDir.'/'.$this->cacheFilename, new NullAdapter()))->warmUp(['map' => $map]);
28+
}
29+
30+
public function isOptional(): bool
31+
{
32+
return false;
33+
}
34+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\UX\LiveComponent\Twig;
6+
7+
use Symfony\Component\Cache\Adapter\NullAdapter;
8+
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
9+
10+
/**
11+
* @author Bart Vanderstukken <bart.vanderstukken@gmail.com>
12+
*/
13+
final class TemplateMap
14+
{
15+
private readonly array $map;
16+
17+
public function __construct(string $cacheFile)
18+
{
19+
$this->map = (new PhpArrayAdapter($cacheFile, new NullAdapter()))->getItem('map')->get();
20+
}
21+
22+
public function resolve(string $obscuredName)
23+
{
24+
return $this->map[$obscuredName] ?? throw new \RuntimeException(sprintf('Cannot find a template matching "%s". Cache may be corrupt.', $obscuredName));
25+
}
26+
27+
public function obscuredName(string $templateName): string
28+
{
29+
$obscuredName = array_search($templateName, $this->map, true);
30+
if (false === $obscuredName) {
31+
throw new \RuntimeException(sprintf('Cannot find a match for template "%s". Cache may be corrupt.', $templateName));
32+
}
33+
34+
return $obscuredName;
35+
}
36+
}

src/LiveComponent/src/Util/LiveControllerAttributesCreator.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\UX\LiveComponent\LiveResponder;
1919
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
2020
use Symfony\UX\LiveComponent\Twig\DeterministicTwigIdCalculator;
21+
use Symfony\UX\LiveComponent\Twig\TemplateMap;
2122
use Symfony\UX\TwigComponent\ComponentAttributes;
2223
use Symfony\UX\TwigComponent\ComponentMetadata;
2324
use Symfony\UX\TwigComponent\MountedComponent;
@@ -47,6 +48,7 @@ public function __construct(
4748
private UrlGeneratorInterface $urlGenerator,
4849
private LiveResponder $liveResponder,
4950
private ?CsrfTokenManagerInterface $csrfTokenManager,
51+
private TemplateMap $templateMap,
5052
) {
5153
}
5254

@@ -82,7 +84,7 @@ public function attributesForRendering(MountedComponent $mounted, ComponentMetad
8284

8385
if ($mounted->hasExtraMetadata('hostTemplate') && $mounted->hasExtraMetadata('embeddedTemplateIndex')) {
8486
$mountedAttributes = $mountedAttributes->defaults([
85-
'data-host-template' => $mounted->getExtraMetadata('hostTemplate'),
87+
'data-host-template' => $this->templateMap->obscuredName($mounted->getExtraMetadata('hostTemplate')),
8688
'data-embedded-template-index' => $mounted->getExtraMetadata('embeddedTemplateIndex'),
8789
]);
8890
}

src/LiveComponent/tests/Functional/EventListener/AddLiveAttributesSubscriberTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\UX\LiveComponent\Tests\Functional\EventListener;
1313

1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
15+
use Symfony\UX\LiveComponent\Tests\LiveComponentTestHelper;
1516
use Zenstruck\Browser\Test\HasBrowser;
1617

1718
/**
@@ -20,6 +21,8 @@
2021
final class AddLiveAttributesSubscriberTest extends KernelTestCase
2122
{
2223
use HasBrowser;
24+
use LiveComponentTestHelper;
25+
2326
/**
2427
* The deterministic id of the "todo_item" components in todo_list.html.twig.
2528
* If that template changes, this will need to be updated.
@@ -82,6 +85,10 @@ public function testCanDisableCsrf(): void
8285

8386
public function testItAddsIdAndFingerprintToChildComponent(): void
8487
{
88+
$templateName = 'components/todo_list.html.twig';
89+
$obscuredName = 'd9bcb8935cbb4282ac5d227fc82ae782';
90+
$this->addTemplateMap($obscuredName, $templateName);
91+
8592
$ul = $this->browser()
8693
->visit('/render-template/render_todo_list')
8794
->assertSuccessful()
@@ -110,6 +117,10 @@ public function testItAddsIdAndFingerprintToChildComponent(): void
110117

111118
public function testItDoesNotOverrideDataLiveIdIfSpecified(): void
112119
{
120+
$templateName = 'components/todo_list.html.twig';
121+
$obscuredName = 'a643d58357b14c9bb077f0c00a742059';
122+
$this->addTemplateMap($obscuredName, $templateName);
123+
113124
$ul = $this->browser()
114125
->visit('/render-template/render_todo_list_with_live_id')
115126
->assertSuccessful()

src/LiveComponent/tests/Functional/EventListener/InterceptChildComponentRenderSubscriberTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ public function testItUsesKeysToRenderChildrenLiveIds(): void
119119
$urlSimple = $this->doBuildUrlForComponent('todo_list_with_keys', []);
120120
$urlWithChangedFingerprints = $this->doBuildUrlForComponent('todo_list_with_keys', $fingerprints);
121121

122+
$templateName = 'components/todo_list_with_keys.html.twig';
123+
$obscuredName = '4726a6e348c4496094a4d3bf3693078e';
124+
$this->addTemplateMap($obscuredName, $templateName);
125+
122126
$this->browser()
123127
->visit($urlSimple)
124128
->assertSuccessful()

src/LiveComponent/tests/Functional/EventListener/LiveComponentSubscriberTest.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,15 @@ public function testCanRenderComponentAsHtmlWithAlternateRoute(): void
8181

8282
public function testCanExecuteComponentActionNormalRoute(): void
8383
{
84+
$templateName = 'render_embedded_with_blocks.html.twig';
85+
$obscuredName = '4bd9245af4594aa28cb77583c29e188e';
86+
$this->addTemplateMap($obscuredName, $templateName);
87+
8488
$dehydrated = $this->dehydrateComponent(
8589
$this->mountComponent(
8690
'component2',
8791
[
88-
'data-host-template' => 'render_embedded_with_blocks.html.twig',
92+
'data-host-template' => $obscuredName,
8993
'data-embedded-template-index' => self::DETERMINISTIC_ID,
9094
]
9195
)
@@ -226,11 +230,15 @@ public function testPreReRenderHookOnlyExecutedDuringAjax(): void
226230

227231
public function testItAddsEmbeddedTemplateContextToEmbeddedComponents(): void
228232
{
233+
$templateName = 'render_embedded_with_blocks.html.twig';
234+
$obscuredName = '1918f197faab43278ba06c0a672a2b97';
235+
$this->addTemplateMap($obscuredName, $templateName);
236+
229237
$dehydrated = $this->dehydrateComponent(
230238
$this->mountComponent(
231239
'component2',
232240
[
233-
'data-host-template' => 'render_embedded_with_blocks.html.twig',
241+
'data-host-template' => $obscuredName,
234242
'data-embedded-template-index' => self::DETERMINISTIC_ID,
235243
]
236244
)
@@ -242,7 +250,7 @@ public function testItAddsEmbeddedTemplateContextToEmbeddedComponents(): void
242250
->assertSee('PreReRenderCalled: No')
243251
->assertSee('Embedded content with access to context, like count=1')
244252
->assertSeeElement('.component2')
245-
->assertElementAttributeContains('.component2', 'data-live-props-value', '"data-host-template":"render_embedded_with_blocks.html.twig"')
253+
->assertElementAttributeContains('.component2', 'data-live-props-value', '"data-host-template":"'.$obscuredName.'"')
246254
->assertElementAttributeContains('.component2', 'data-live-props-value', '"data-embedded-template-index":'.self::DETERMINISTIC_ID)
247255
->get('/_components/component2?props='.urlencode(json_encode($dehydrated->getProps())))
248256
->assertSuccessful()
@@ -253,11 +261,16 @@ public function testItAddsEmbeddedTemplateContextToEmbeddedComponents(): void
253261

254262
public function testItUseBlocksFromEmbeddedContextUsingMultipleComponents(): void
255263
{
264+
$templateName = 'render_multiple_embedded_with_blocks.html.twig';
265+
$obscuredName = '5c474b02358c46cca3da7340cc79cc2e';
266+
267+
$this->addTemplateMap($obscuredName, $templateName);
268+
256269
$dehydrated = $this->dehydrateComponent(
257270
$this->mountComponent(
258271
'component2',
259272
[
260-
'data-host-template' => 'render_multiple_embedded_with_blocks.html.twig',
273+
'data-host-template' => $obscuredName,
261274
'data-embedded-template-index' => self::DETERMINISTIC_ID_MULTI_2,
262275
]
263276
)

src/LiveComponent/tests/LiveComponentTestHelper.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace Symfony\UX\LiveComponent\Tests;
1313

14+
use Symfony\Component\Cache\Adapter\NullAdapter;
15+
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
16+
use Symfony\UX\LiveComponent\DependencyInjection\LiveComponentExtension;
1417
use Symfony\UX\LiveComponent\LiveComponentHydrator;
1518
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
1619
use Symfony\UX\LiveComponent\Util\DehydratedProps;
@@ -66,4 +69,14 @@ private function hydrateComponent(object $component, string $name, array $props,
6669

6770
return $this->hydrator()->hydrate($component, $props, $updatedProps, $liveMetadataFactory->getMetadata($name));
6871
}
72+
73+
private function addTemplateMap(string $obscuredName, string $templateName): void
74+
{
75+
if (!self::$booted) {
76+
self::bootKernel();
77+
}
78+
79+
$phpArrayAdapter = new PhpArrayAdapter(self::$kernel->getCacheDir().'/'.LiveComponentExtension::TEMPLATES_MAP_FILENAME, new NullAdapter());
80+
$phpArrayAdapter->warmUp(['map' => [$obscuredName => $templateName]]);
81+
}
6982
}

0 commit comments

Comments
 (0)