Skip to content

Commit 9a8f599

Browse files
committed
bug #1082 [TwigComponent][LiveComponent] Fix Live embedded component within namespaced template (sneakyvv)
This PR was merged into the 2.x branch. Discussion ---------- [TwigComponent][LiveComponent] Fix Live embedded component within namespaced template | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Tickets | | License | MIT This fix is related to #1070 . That PR made sure that a namespaced template could also be found in the TemplateMap, which contains paths without the namespace, so that the right template name (or actually obscuring hash) is used for the host template reference for live embedded components within such a template (which is used to find the right blocks during a re-render). However, because the Parser was still using the namespaced template name to calculate the deterministic index number for an embedded template, it would not match with the index of the stripped / namespace-less template name, causing it to have a different index for the embedded template during re-render then during parsing, resulting in a template not found error. This PR now also strips namespaces from template names during parsing. Commits ------- ad84cf7 [TwigComponent] Fix Live embedded component with namespace
2 parents 14115f1 + ad84cf7 commit 9a8f599

File tree

4 files changed

+67
-18
lines changed

4 files changed

+67
-18
lines changed

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,45 @@ public function testItUseBlocksFromEmbeddedContextUsingMultipleComponents(): voi
311311
;
312312
}
313313

314+
public function testItUseBlocksFromEmbeddedContextUsingMultipleComponentsWithNamespacedTemplate(): void
315+
{
316+
$templateName = 'render_multiple_embedded_with_blocks.html.twig';
317+
$obscuredName = '5c474b02358c46cca3da7340cc79cc2e';
318+
319+
$this->addTemplateMap($obscuredName, $templateName);
320+
321+
$dehydrated = $this->dehydrateComponent(
322+
$this->mountComponent(
323+
'component2',
324+
[
325+
'data-host-template' => $obscuredName,
326+
'data-embedded-template-index' => self::DETERMINISTIC_ID_MULTI_2,
327+
]
328+
)
329+
);
330+
331+
$token = null;
332+
333+
$this->browser()
334+
->visit('/render-namespaced-template/render_multiple_embedded_with_blocks')
335+
->assertSuccessful()
336+
->assertSeeIn('#component1', 'Overridden content from component 1')
337+
->assertSeeIn('#component2', 'Overridden content from component 2 on same line - count: 1')
338+
->assertSeeIn('#component3', 'PreReRenderCalled: No')
339+
->use(function (Crawler $crawler) use (&$token) {
340+
// get a valid token to use for actions
341+
$token = $crawler->filter('div')->eq(1)->attr('data-live-csrf-value');
342+
})
343+
->post('/_components/component2/increase', [
344+
'headers' => ['X-CSRF-TOKEN' => $token],
345+
'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
346+
])
347+
->assertSuccessful()
348+
->assertHeaderContains('Content-Type', 'html')
349+
->assertSee('Overridden content from component 2 on same line - count: 2')
350+
;
351+
}
352+
314353
public function testCanRedirectFromComponentAction(): void
315354
{
316355
$dehydrated = $this->dehydrateComponent($this->mountComponent('component2'));

src/TwigComponent/src/Twig/ComponentNode.php

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function compile(Compiler $compiler): void
6868
->raw('), ')
6969
->raw($this->getAttribute('only') ? '[]' : '$context')
7070
->raw(', ')
71-
->string($this->parseTemplateName($this->getAttribute('name')))
71+
->string(TemplateNameParser::parse($this->getAttribute('name')))
7272
->raw(', ')
7373
->raw($this->getAttribute('index'))
7474
->raw(");\n")
@@ -91,20 +91,4 @@ public function compile(Compiler $compiler): void
9191
->raw("\n")
9292
;
9393
}
94-
95-
/**
96-
* Copied from Twig\Loader\FilesystemLoader, and adjusted to needs for this class.
97-
*/
98-
private function parseTemplateName(string $name): mixed
99-
{
100-
if (isset($name[0]) && '@' == $name[0]) {
101-
if (false === $pos = strpos($name, '/')) {
102-
throw new \LogicException(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
103-
}
104-
105-
return substr($name, $pos + 1);
106-
}
107-
108-
return $name;
109-
}
11094
}

src/TwigComponent/src/Twig/ComponentTokenParser.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public function parse(Token $token): Node
8787
$this->parser->embedTemplate($module);
8888

8989
// use deterministic index for the embedded template, so it can be loaded in a controlled manner
90-
$module->setAttribute('index', $this->generateEmbeddedTemplateIndex($stream->getSourceContext()->getName(), $token->getLine()));
90+
$module->setAttribute('index', $this->generateEmbeddedTemplateIndex(TemplateNameParser::parse($stream->getSourceContext()->getName()), $token->getLine()));
9191

9292
$stream->expect(Token::BLOCK_END_TYPE);
9393

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\UX\TwigComponent\Twig;
6+
7+
final class TemplateNameParser
8+
{
9+
/**
10+
* Copied from Twig\Loader\FilesystemLoader, and adjusted to needs for this class (no namespace returned).
11+
*
12+
* @see \Twig\Loader\FilesystemLoader::parseName
13+
*/
14+
public static function parse(string $name): mixed
15+
{
16+
if (isset($name[0]) && '@' == $name[0]) {
17+
if (false === $pos = strpos($name, '/')) {
18+
throw new \LogicException(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
19+
}
20+
21+
return substr($name, $pos + 1);
22+
}
23+
24+
return $name;
25+
}
26+
}

0 commit comments

Comments
 (0)