Skip to content

Commit a6f28a6

Browse files
committed
[Twig] "Anonymous" components
1 parent 18b194b commit a6f28a6

File tree

3 files changed

+56
-9
lines changed

3 files changed

+56
-9
lines changed

src/TwigComponent/src/ComponentRenderer.php

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;
1717
use Symfony\UX\TwigComponent\EventListener\PreRenderEvent;
1818
use Twig\Environment;
19+
use Twig\Error\LoaderError;
1920
use Twig\Extension\EscaperExtension;
2021

2122
/**
@@ -25,24 +26,17 @@
2526
*/
2627
final class ComponentRenderer
2728
{
28-
private bool $safeClassesRegistered = false;
29-
3029
public function __construct(
3130
private Environment $twig,
3231
private EventDispatcherInterface $dispatcher,
3332
private ComponentFactory $factory,
3433
private PropertyAccessorInterface $propertyAccessor
3534
) {
35+
$this->twig->getExtension(EscaperExtension::class)->addSafeClass(ComponentAttributes::class, ['html']);
3636
}
3737

3838
public function render(MountedComponent $mounted): string
3939
{
40-
if (!$this->safeClassesRegistered) {
41-
$this->twig->getExtension(EscaperExtension::class)->addSafeClass(ComponentAttributes::class, ['html']);
42-
43-
$this->safeClassesRegistered = true;
44-
}
45-
4640
$component = $mounted->getComponent();
4741
$metadata = $this->factory->metadataFor($mounted->getName());
4842
$variables = array_merge(
@@ -65,6 +59,18 @@ public function render(MountedComponent $mounted): string
6559
return $this->twig->render($event->getTemplate(), $event->getVariables());
6660
}
6761

62+
public function renderAnonymous(string $template, array $data): string
63+
{
64+
// TODO configurable attributes - global option for anon components?
65+
$data['attributes'] = new ComponentAttributes($data['attributes'] ?? []);
66+
67+
try {
68+
return $this->twig->render($template, $data);
69+
} catch (LoaderError) {
70+
throw new \RuntimeException(); // todo user mistyped the component name, mistyped the component template, didn't autowire component...
71+
}
72+
}
73+
6874
private function exposedVariables(object $component, bool $exposePublicProps): \Iterator
6975
{
7076
if ($exposePublicProps) {

src/TwigComponent/src/Twig/ComponentRuntime.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public function __construct(ComponentFactory $componentFactory, ComponentRendere
3232

3333
public function render(string $name, array $props = []): string
3434
{
35-
return $this->componentRenderer->render($this->componentFactory->create($name, $props));
35+
try {
36+
return $this->componentRenderer->render($this->componentFactory->create($name, $props));
37+
} catch (\InvalidArgumentException) {
38+
// component not found, try as anonymous component
39+
return $this->componentRenderer->renderAnonymous($name, $props);
40+
}
3641
}
3742
}

src/TwigComponent/tests/Integration/ComponentExtensionTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1515
use Twig\Environment;
16+
use Twig\Error\RuntimeError;
1617

1718
/**
1819
* @author Kevin Bond <kevinbond@gmail.com>
@@ -118,6 +119,41 @@ public function testCanDisableExposingPublicProps(): void
118119
$this->assertStringContainsString('NoPublicProp1: default', $output);
119120
}
120121

122+
public function testCanRenderAnonymousComponent(): void
123+
{
124+
$output = $this->renderComponent('components/with_attributes.html.twig', [
125+
'prop' => 'prop value 1',
126+
'attributes' => [
127+
'class' => 'bar',
128+
'style' => 'color:red;',
129+
'value' => '',
130+
'autofocus' => null,
131+
],
132+
]);
133+
134+
$this->assertStringContainsString('Component Content (prop value 1)', $output);
135+
$this->assertStringContainsString('<button class="foo bar" type="button" style="color:red;" value="" autofocus>', $output);
136+
137+
$output = $this->renderComponent('components/with_attributes.html.twig', [
138+
'prop' => 'prop value 2',
139+
'attributes' => [
140+
'class' => 'baz',
141+
'type' => 'submit',
142+
'style' => 'color:red;',
143+
],
144+
]);
145+
146+
$this->assertStringContainsString('Component Content (prop value 2)', $output);
147+
$this->assertStringContainsString('<button class="foo baz" type="submit" style="color:red;">', $output);
148+
}
149+
150+
public function testInvalidAnonymousComponent(): void
151+
{
152+
$this->expectException(RuntimeError::class);
153+
154+
$this->renderComponent('invalid.html.twig');
155+
}
156+
121157
private function renderComponent(string $name, array $data = []): string
122158
{
123159
return self::getContainer()->get(Environment::class)->render('render_component.html.twig', [

0 commit comments

Comments
 (0)