From a6f28a6c164a0329b97dfc5a8b3368b3496d08ad Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Fri, 18 Feb 2022 16:07:00 -0500 Subject: [PATCH] [Twig] "Anonymous" components --- src/TwigComponent/src/ComponentRenderer.php | 22 +++++++----- .../src/Twig/ComponentRuntime.php | 7 +++- .../Integration/ComponentExtensionTest.php | 36 +++++++++++++++++++ 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/TwigComponent/src/ComponentRenderer.php b/src/TwigComponent/src/ComponentRenderer.php index 7950a32dd3a..bb4ba5b66aa 100644 --- a/src/TwigComponent/src/ComponentRenderer.php +++ b/src/TwigComponent/src/ComponentRenderer.php @@ -16,6 +16,7 @@ use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate; use Symfony\UX\TwigComponent\EventListener\PreRenderEvent; use Twig\Environment; +use Twig\Error\LoaderError; use Twig\Extension\EscaperExtension; /** @@ -25,24 +26,17 @@ */ final class ComponentRenderer { - private bool $safeClassesRegistered = false; - public function __construct( private Environment $twig, private EventDispatcherInterface $dispatcher, private ComponentFactory $factory, private PropertyAccessorInterface $propertyAccessor ) { + $this->twig->getExtension(EscaperExtension::class)->addSafeClass(ComponentAttributes::class, ['html']); } public function render(MountedComponent $mounted): string { - if (!$this->safeClassesRegistered) { - $this->twig->getExtension(EscaperExtension::class)->addSafeClass(ComponentAttributes::class, ['html']); - - $this->safeClassesRegistered = true; - } - $component = $mounted->getComponent(); $metadata = $this->factory->metadataFor($mounted->getName()); $variables = array_merge( @@ -65,6 +59,18 @@ public function render(MountedComponent $mounted): string return $this->twig->render($event->getTemplate(), $event->getVariables()); } + public function renderAnonymous(string $template, array $data): string + { + // TODO configurable attributes - global option for anon components? + $data['attributes'] = new ComponentAttributes($data['attributes'] ?? []); + + try { + return $this->twig->render($template, $data); + } catch (LoaderError) { + throw new \RuntimeException(); // todo user mistyped the component name, mistyped the component template, didn't autowire component... + } + } + private function exposedVariables(object $component, bool $exposePublicProps): \Iterator { if ($exposePublicProps) { diff --git a/src/TwigComponent/src/Twig/ComponentRuntime.php b/src/TwigComponent/src/Twig/ComponentRuntime.php index f12af5c9e53..5aa324c9ef0 100644 --- a/src/TwigComponent/src/Twig/ComponentRuntime.php +++ b/src/TwigComponent/src/Twig/ComponentRuntime.php @@ -32,6 +32,11 @@ public function __construct(ComponentFactory $componentFactory, ComponentRendere public function render(string $name, array $props = []): string { - return $this->componentRenderer->render($this->componentFactory->create($name, $props)); + try { + return $this->componentRenderer->render($this->componentFactory->create($name, $props)); + } catch (\InvalidArgumentException) { + // component not found, try as anonymous component + return $this->componentRenderer->renderAnonymous($name, $props); + } } } diff --git a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php index bb1bb4cbf55..52d8822b249 100644 --- a/src/TwigComponent/tests/Integration/ComponentExtensionTest.php +++ b/src/TwigComponent/tests/Integration/ComponentExtensionTest.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Twig\Environment; +use Twig\Error\RuntimeError; /** * @author Kevin Bond @@ -118,6 +119,41 @@ public function testCanDisableExposingPublicProps(): void $this->assertStringContainsString('NoPublicProp1: default', $output); } + public function testCanRenderAnonymousComponent(): void + { + $output = $this->renderComponent('components/with_attributes.html.twig', [ + 'prop' => 'prop value 1', + 'attributes' => [ + 'class' => 'bar', + 'style' => 'color:red;', + 'value' => '', + 'autofocus' => null, + ], + ]); + + $this->assertStringContainsString('Component Content (prop value 1)', $output); + $this->assertStringContainsString('