Skip to content

Commit 45c0ffe

Browse files
committed
feature #2168 [TwigComponent] Use a RuntimeExtension (smnandre)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [TwigComponent] Use a RuntimeExtension * Hide implementation & start decoupling the template from the event system * Optimize (a bit) the ComponentNode Commits ------- 8e99e147 [TwigComponent] Use a RuntimeExtension
2 parents af6d391 + f1319fe commit 45c0ffe

6 files changed

+91
-86
lines changed

src/DependencyInjection/TwigComponentExtension.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use Symfony\UX\TwigComponent\DependencyInjection\Compiler\TwigComponentPass;
3535
use Symfony\UX\TwigComponent\Twig\ComponentExtension;
3636
use Symfony\UX\TwigComponent\Twig\ComponentLexer;
37+
use Symfony\UX\TwigComponent\Twig\ComponentRuntime;
3738
use Symfony\UX\TwigComponent\Twig\TwigEnvironmentConfigurator;
3839

3940
/**
@@ -102,7 +103,13 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(\sprintf('Added in
102103

103104
$container->register('ux.twig_component.twig.component_extension', ComponentExtension::class)
104105
->addTag('twig.extension')
105-
->addTag('container.service_subscriber', ['key' => ComponentRenderer::class, 'id' => 'ux.twig_component.component_renderer'])
106+
;
107+
108+
$container->register('.ux.twig_component.twig.component_runtime', ComponentRuntime::class)
109+
->setArguments([
110+
new Reference('ux.twig_component.component_renderer'),
111+
])
112+
->addTag('twig.runtime')
106113
;
107114

108115
$container->register('ux.twig_component.twig.lexer', ComponentLexer::class);

src/Twig/ComponentExtension.php

+2-60
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,8 @@
1111

1212
namespace Symfony\UX\TwigComponent\Twig;
1313

14-
use Psr\Container\ContainerInterface;
15-
use Symfony\Contracts\Service\ServiceSubscriberInterface;
16-
use Symfony\UX\TwigComponent\ComponentRenderer;
1714
use Symfony\UX\TwigComponent\CVA;
18-
use Symfony\UX\TwigComponent\Event\PreRenderEvent;
1915
use Twig\DeprecatedCallableInfo;
20-
use Twig\Error\RuntimeError;
2116
use Twig\Extension\AbstractExtension;
2217
use Twig\TwigFunction;
2318

@@ -26,23 +21,12 @@
2621
*
2722
* @internal
2823
*/
29-
final class ComponentExtension extends AbstractExtension implements ServiceSubscriberInterface
24+
final class ComponentExtension extends AbstractExtension
3025
{
31-
public function __construct(private ContainerInterface $container)
32-
{
33-
}
34-
35-
public static function getSubscribedServices(): array
36-
{
37-
return [
38-
ComponentRenderer::class,
39-
];
40-
}
41-
4226
public function getFunctions(): array
4327
{
4428
return [
45-
new TwigFunction('component', [$this, 'render'], ['is_safe' => ['all']]),
29+
new TwigFunction('component', [ComponentRuntime::class, 'render'], ['is_safe' => ['all']]),
4630
new TwigFunction('cva', [$this, 'cva'], [
4731
...(class_exists(DeprecatedCallableInfo::class)
4832
? ['deprecation_info' => new DeprecatedCallableInfo('symfony/ux-twig-component', '2.20', 'html_cva', 'twig/html-extra')]
@@ -59,38 +43,6 @@ public function getTokenParsers(): array
5943
];
6044
}
6145

62-
public function render(string $name, array $props = []): string
63-
{
64-
try {
65-
return $this->container->get(ComponentRenderer::class)->createAndRender($name, $props);
66-
} catch (\Throwable $e) {
67-
$this->throwRuntimeError($name, $e);
68-
}
69-
}
70-
71-
public function extensionPreCreateForRender(string $name, array $props): ?string
72-
{
73-
try {
74-
return $this->container->get(ComponentRenderer::class)->preCreateForRender($name, $props);
75-
} catch (\Throwable $e) {
76-
$this->throwRuntimeError($name, $e);
77-
}
78-
}
79-
80-
public function startEmbeddedComponentRender(string $name, array $props, array $context, string $hostTemplateName, int $index): PreRenderEvent
81-
{
82-
try {
83-
return $this->container->get(ComponentRenderer::class)->startEmbeddedComponentRender($name, $props, $context, $hostTemplateName, $index);
84-
} catch (\Throwable $e) {
85-
$this->throwRuntimeError($name, $e);
86-
}
87-
}
88-
89-
public function finishEmbeddedComponentRender(): void
90-
{
91-
$this->container->get(ComponentRenderer::class)->finishEmbeddedComponentRender();
92-
}
93-
9446
/**
9547
* Create a CVA instance.
9648
*
@@ -119,14 +71,4 @@ public function cva(array $cva): CVA
11971
$cva['defaultVariants'] ?? [],
12072
);
12173
}
122-
123-
private function throwRuntimeError(string $name, \Throwable $e): void
124-
{
125-
// if it's already a Twig RuntimeError, just rethrow it
126-
if ($e instanceof RuntimeError) {
127-
throw $e;
128-
}
129-
130-
throw new RuntimeError(\sprintf('Error rendering "%s" component: "%s"', $name, $e->getMessage()), previous: $e);
131-
}
13274
}

src/Twig/ComponentNode.php

+20-24
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,30 @@ public function compile(Compiler $compiler): void
4848
{
4949
$compiler->addDebugInfo($this);
5050

51+
$useYield = method_exists(Environment::class, 'useYield') && $compiler->getEnvironment()->useYield();
52+
5153
// since twig/twig 3.9.0: Using the internal "twig_to_array" function is deprecated.
5254
if (method_exists(CoreExtension::class, 'toArray')) {
5355
$twig_to_array = 'Twig\Extension\CoreExtension::toArray';
5456
} else {
5557
$twig_to_array = 'twig_to_array';
5658
}
5759

60+
$componentRuntime = $compiler->getVarName();
61+
62+
$compiler
63+
->write(\sprintf('$%s = $this->env->getRuntime(', $componentRuntime))
64+
->string(ComponentRuntime::class)
65+
->raw(");\n");
66+
5867
/*
5968
* Block 1) PreCreateForRender handling
6069
*
6170
* We call code to trigger the PreCreateForRender event. If the event returns
6271
* a string, we return that string and skip the rest of the rendering process.
6372
*/
6473
$compiler
65-
->write('$preRendered = $this->extensions[')
66-
->string(ComponentExtension::class)
67-
->raw(']->extensionPreCreateForRender(')
74+
->write(\sprintf('$preRendered = $%s->preRender(', $componentRuntime))
6875
->string($this->getAttribute('component'))
6976
->raw(', ')
7077
->raw($twig_to_array)
@@ -96,9 +103,7 @@ public function compile(Compiler $compiler): void
96103
* the final template, template index & variables.
97104
*/
98105
$compiler
99-
->write('$preRenderEvent = $this->extensions[')
100-
->string(ComponentExtension::class)
101-
->raw(']->startEmbeddedComponentRender(')
106+
->write(\sprintf('$preRenderEvent = $%s->startEmbedComponent(', $componentRuntime))
102107
->string($this->getAttribute('component'))
103108
->raw(', ')
104109
->raw($twig_to_array)
@@ -111,6 +116,7 @@ public function compile(Compiler $compiler): void
111116
->raw(', ')
112117
->raw($this->getAttribute('embedded_index'))
113118
->raw(");\n");
119+
114120
$compiler
115121
->write('$embeddedContext = $preRenderEvent->getVariables();')
116122
->raw("\n")
@@ -132,18 +138,11 @@ public function compile(Compiler $compiler): void
132138
* We add the outerBlock to the context if it doesn't exist yet.
133139
* Then add them to the block stack and get the converted embedded blocks.
134140
*/
135-
$compiler->write('if (!isset($embeddedContext["outerBlocks"])) {')
136-
->raw("\n")
137-
->indent()
138-
->write(\sprintf('$embeddedContext["outerBlocks"] = new \%s();', BlockStack::class))
139-
->raw("\n")
140-
->outdent()
141-
->write('}')
141+
$compiler
142+
->write(\sprintf('$embeddedContext["outerBlocks"] ??= new \%s();', BlockStack::class))
142143
->raw("\n");
143144

144-
$compiler->write('$embeddedBlocks = $embeddedContext[')
145-
->string('outerBlocks')
146-
->raw(']->convert($blocks, ')
145+
$compiler->write('$embeddedBlocks = $embeddedContext["outerBlocks"]->convert($blocks, ')
147146
->raw($this->getAttribute('embedded_index'))
148147
->raw(");\n");
149148

@@ -152,9 +151,8 @@ public function compile(Compiler $compiler): void
152151
*
153152
* This will actually render the child component template.
154153
*/
155-
if (method_exists(Environment::class, 'useYield') && $compiler->getEnvironment()->useYield()) {
156-
$compiler
157-
->write('yield from ');
154+
if ($useYield) {
155+
$compiler->write('yield from ');
158156
}
159157
$compiler
160158
->write('$this->loadTemplate(')
@@ -167,7 +165,7 @@ public function compile(Compiler $compiler): void
167165
->string($this->getAttribute('embedded_index'))
168166
->raw(')');
169167

170-
if (method_exists(Environment::class, 'useYield') && $compiler->getEnvironment()->useYield()) {
168+
if ($useYield) {
171169
$compiler->raw('->unwrap()->yield(');
172170
} else {
173171
$compiler->raw('->display(');
@@ -176,10 +174,8 @@ public function compile(Compiler $compiler): void
176174
->raw('$embeddedContext, $embeddedBlocks')
177175
->raw(");\n");
178176

179-
$compiler->write('$this->extensions[')
180-
->string(ComponentExtension::class)
181-
->raw(']->finishEmbeddedComponentRender()')
182-
->raw(";\n")
177+
$compiler->write(\sprintf('$%s->finishEmbedComponent();', $componentRuntime))
178+
->raw("\n")
183179
;
184180

185181
$compiler

src/Twig/ComponentRuntime.php

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\TwigComponent\Twig;
13+
14+
use Symfony\UX\TwigComponent\ComponentRenderer;
15+
use Symfony\UX\TwigComponent\Event\PreRenderEvent;
16+
17+
/**
18+
* @author Kevin Bond <[email protected]>
19+
* @author Simon André <[email protected]>
20+
*
21+
* @internal
22+
*/
23+
final class ComponentRuntime
24+
{
25+
public function __construct(
26+
private readonly ComponentRenderer $renderer,
27+
) {
28+
}
29+
30+
/**
31+
* @param array<string, mixed> $props
32+
*/
33+
public function render(string $name, array $props = []): string
34+
{
35+
return $this->renderer->createAndRender($name, $props);
36+
}
37+
38+
/**
39+
* @param array<string, mixed> $props
40+
*/
41+
public function preRender(string $name, array $props): ?string
42+
{
43+
return $this->renderer->preCreateForRender($name, $props);
44+
}
45+
46+
/**
47+
* @param array<string, mixed> $props
48+
* @param array<string, mixed> $context
49+
*/
50+
public function startEmbedComponent(string $name, array $props, array $context, string $hostTemplateName, int $index): PreRenderEvent
51+
{
52+
return $this->renderer->startEmbeddedComponentRender($name, $props, $context, $hostTemplateName, $index);
53+
}
54+
55+
public function finishEmbedComponent(): void
56+
{
57+
$this->renderer->finishEmbeddedComponentRender();
58+
}
59+
}

src/Twig/PropsTokenParser.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function parse(Token $token): Node
4444
}
4545
}
4646

47-
return new PropsNode($names, $values, $token->getLine(), $token->getValue());
47+
return new PropsNode($names, $values, $token->getLine());
4848
}
4949

5050
public function getTag(): string

src/Twig/TwigEnvironmentConfigurator.php

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public function __construct(
3030
public function configure(Environment $environment): void
3131
{
3232
$this->decorated->configure($environment);
33+
3334
$environment->setLexer(new ComponentLexer($environment));
3435

3536
if (class_exists(EscaperRuntime::class)) {

0 commit comments

Comments
 (0)