Skip to content

Commit 0446191

Browse files
sneakyvvweaverryan
authored andcommitted
[TwigComponent] [LiveComponent] Add support for embedded live components
1 parent 0c222dd commit 0446191

File tree

5 files changed

+110
-20
lines changed

5 files changed

+110
-20
lines changed

src/ComponentRenderer.php

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\UX\TwigComponent;
1313

14+
use Composer\InstalledVersions;
1415
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1516
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
1617
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;
@@ -38,14 +39,21 @@ public function __construct(
3839
) {
3940
}
4041

41-
public function createAndRender(string $name, array $props = []): string
42+
/**
43+
* Allow the render process to be short-circuited.
44+
*/
45+
public function preCreateForRender(string $name, array $props = []): ?string
4246
{
4347
$event = new PreCreateForRenderEvent($name, $props);
4448
$this->dispatcher->dispatch($event);
4549

46-
// allow the process to be short-circuited
47-
if (null !== $rendered = $event->getRenderedString()) {
48-
return $rendered;
50+
return $event->getRenderedString();
51+
}
52+
53+
public function createAndRender(string $name, array $props = []): string
54+
{
55+
if ($preRendered = $this->preCreateForRender($name, $props)) {
56+
return $preRendered;
4957
}
5058

5159
return $this->render($this->factory->create($name, $props));
@@ -58,7 +66,15 @@ public function render(MountedComponent $mounted): string
5866
$event = $this->preRender($mounted);
5967

6068
try {
61-
return $this->twig->render($event->getTemplate(), $event->getVariables());
69+
if (InstalledVersions::getVersion('twig/twig') < 3) {
70+
return $this->twig->loadTemplate($event->getTemplate(), $event->getTemplateIndex())->render($event->getVariables());
71+
}
72+
73+
return $this->twig->loadTemplate(
74+
$this->twig->getTemplateClass($event->getTemplate()),
75+
$event->getTemplate(),
76+
$event->getTemplateIndex(),
77+
)->render($event->getVariables());
6278
} finally {
6379
$this->componentStack->pop();
6480

@@ -67,17 +83,27 @@ public function render(MountedComponent $mounted): string
6783
}
6884
}
6985

70-
public function embeddedContext(string $name, array $props, array $context): array
86+
public function embeddedContext(string $name, array $props, array $context, string $hostTemplateName, int $index): array
7187
{
7288
$context[PreRenderEvent::EMBEDDED] = true;
7389

74-
$embeddedContext = $this->preRender($this->factory->create($name, $props), $context)->getVariables();
90+
$mounted = $this->factory->create($name, $props);
91+
$mounted->addExtraMetadata('hostTemplate', $hostTemplateName);
92+
$mounted->addExtraMetadata('embeddedTemplateIndex', $index);
7593

76-
if (!isset($embeddedContext['outerBlocks'])) {
77-
$embeddedContext['outerBlocks'] = new BlockStack();
78-
}
94+
$this->componentStack->push($mounted);
95+
96+
try {
97+
$embeddedContext = $this->preRender($mounted, $context)->getVariables();
98+
99+
if (!isset($embeddedContext['outerBlocks'])) {
100+
$embeddedContext['outerBlocks'] = new BlockStack();
101+
}
79102

80-
return $embeddedContext;
103+
return $embeddedContext;
104+
} finally {
105+
$this->componentStack->pop();
106+
}
81107
}
82108

83109
private function preRender(MountedComponent $mounted, array $context = []): PreRenderEvent

src/Event/PreRenderEvent.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ final class PreRenderEvent extends Event
2525

2626
private string $template;
2727

28+
private ?int $templateIndex = null;
29+
2830
/**
2931
* @internal
3032
*/
@@ -52,17 +54,22 @@ public function getTemplate(): string
5254
/**
5355
* Change the twig template used.
5456
*/
55-
public function setTemplate(string $template): self
57+
public function setTemplate(string $template, int $index = null): self
5658
{
57-
if ($this->isEmbedded()) {
58-
throw new \LogicException('Cannot modify template for embedded components.');
59-
}
60-
6159
$this->template = $template;
60+
$this->templateIndex = $index;
6261

6362
return $this;
6463
}
6564

65+
/**
66+
* @return string The twig template index used for the component, in case it's an embedded template
67+
*/
68+
public function getTemplateIndex(): ?int
69+
{
70+
return $this->templateIndex;
71+
}
72+
6673
public function getComponent(): object
6774
{
6875
return $this->mounted->getComponent();

src/Twig/ComponentExtension.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,19 @@ public function render(string $name, array $props = []): string
6161
}
6262
}
6363

64-
public function embeddedContext(string $name, array $props, array $context): array
64+
public function preRender(string $name, array $props): ?string
6565
{
6666
try {
67-
return $this->container->get(ComponentRenderer::class)->embeddedContext($name, $props, $context);
67+
return $this->container->get(ComponentRenderer::class)->preCreateForRender($name, $props);
68+
} catch (\Throwable $e) {
69+
$this->throwRuntimeError($name, $e);
70+
}
71+
}
72+
73+
public function embeddedContext(string $name, array $props, array $context, string $hostTemplateName, int $index): array
74+
{
75+
try {
76+
return $this->container->get(ComponentRenderer::class)->embeddedContext($name, $props, $context, $hostTemplateName, $index);
6877
} catch (\Throwable $e) {
6978
$this->throwRuntimeError($name, $e);
7079
}

src/Twig/ComponentNode.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,42 @@ public function compile(Compiler $compiler): void
3535
$compiler->addDebugInfo($this);
3636

3737
$compiler
38-
->write('$embeddedContext = $this->extensions[')
38+
->write('$preRendered = $this->extensions[')
3939
->string(ComponentExtension::class)
40-
->raw(']->embeddedContext(')
40+
->raw(']->preRender(')
4141
->string($this->getAttribute('component'))
4242
->raw(', ')
4343
->raw('twig_to_array(')
4444
->subcompile($this->getNode('variables'))
45+
->raw(')')
46+
->raw(");\n")
47+
;
48+
49+
$compiler
50+
->write('if (null !== $preRendered) {')
51+
->raw("\n")
52+
->indent()
53+
->write('echo $preRendered;')
54+
->raw("\n")
55+
->outdent()
56+
->write('} else {')
57+
->raw("\n")
58+
->indent()
59+
;
60+
61+
$compiler
62+
->write('$embeddedContext = $this->extensions[')
63+
->string(ComponentExtension::class)
64+
->raw(']->embeddedContext(')
65+
->string($this->getAttribute('component'))
66+
->raw(', twig_to_array(')
67+
->subcompile($this->getNode('variables'))
4568
->raw('), ')
4669
->raw($this->getAttribute('only') ? '[]' : '$context')
70+
->raw(', ')
71+
->string($this->getAttribute('name'))
72+
->raw(', ')
73+
->raw($this->getAttribute('index'))
4774
->raw(");\n")
4875
;
4976

@@ -57,5 +84,11 @@ public function compile(Compiler $compiler): void
5784
$this->addGetTemplate($compiler);
5885
$compiler->raw('->display($embeddedContext, $embeddedBlocks);');
5986
$compiler->raw("\n");
87+
88+
$compiler
89+
->outdent()
90+
->write('}')
91+
->raw("\n")
92+
;
6093
}
6194
}

src/Twig/ComponentTokenParser.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ final class ComponentTokenParser extends AbstractTokenParser
3232
/** @var ComponentFactory|callable():ComponentFactory */
3333
private $factory;
3434

35+
private array $lineAndFileCounts = [];
36+
3537
/**
3638
* @param callable():ComponentFactory $factory
3739
*/
@@ -84,6 +86,9 @@ public function parse(Token $token): Node
8486

8587
$this->parser->embedTemplate($module);
8688

89+
// 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()));
91+
8792
$stream->expect(Token::BLOCK_END_TYPE);
8893

8994
return new ComponentNode($componentName, $module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $token->getLine(), $this->getTag());
@@ -136,4 +141,14 @@ private function parseArguments(): array
136141

137142
return [$variables, $only];
138143
}
144+
145+
private function generateEmbeddedTemplateIndex(string $file, int $line): int
146+
{
147+
$fileAndLine = sprintf('%s-%d', $file, $line);
148+
if (!isset($this->lineAndFileCounts[$fileAndLine])) {
149+
$this->lineAndFileCounts[$fileAndLine] = 0;
150+
}
151+
152+
return crc32($fileAndLine).++$this->lineAndFileCounts[$fileAndLine];
153+
}
139154
}

0 commit comments

Comments
 (0)