Skip to content

Commit 04b4c30

Browse files
committed
Render Statics Components and Props tags
1 parent 1b7020e commit 04b4c30

13 files changed

+158
-25
lines changed

src/TwigComponent/src/ComponentFactory.php

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,12 @@ final class ComponentFactory
3131
* @param array<class-string, string> $classMap
3232
*/
3333
public function __construct(
34+
private Environment $environment,
3435
private ServiceLocator $components,
3536
private PropertyAccessorInterface $propertyAccessor,
3637
private EventDispatcherInterface $eventDispatcher,
3738
private array $config,
38-
private array $classMap,
39-
private Environment $environment,
40-
private string $twigExtension
39+
private array $classMap
4140
) {
4241
}
4342

@@ -46,14 +45,14 @@ public function metadataFor(string $name): ComponentMetadata
4645
$name = $this->classMap[$name] ?? $name;
4746

4847
if (!$config = $this->config[$name] ?? null) {
49-
if ($this->isStaticComponent($name)) {
48+
if (($template = $this->findStaticComponentTemplate($name)) !== null) {
5049
return new ComponentMetadata([
5150
'key' => $name,
52-
'template' => $this->getTemplateFromName($name),
51+
'template' => $template,
5352
]);
5453
}
5554

56-
throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no template %s. founded', $name, implode(', ', array_keys($this->config)), $this->getTemplateFromName($name)));
55+
throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no template anonymous component founded', $name, implode(', ', array_keys($this->config))));
5756
}
5857

5958
return new ComponentMetadata($config);
@@ -134,6 +133,12 @@ private function mount(object $component, array &$data): void
134133
return;
135134
}
136135

136+
if ($component instanceof StaticComponent) {
137+
$component->mount($data);
138+
139+
return;
140+
}
141+
137142
$parameters = [];
138143

139144
foreach ($method->getParameters() as $refParameter) {
@@ -163,7 +168,7 @@ private function getComponent(string $name): object
163168
return new StaticComponent();
164169
}
165170

166-
throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no template %s. founded', $name, implode(', ', array_keys($this->components->getProvidedServices())), $this->getTemplateFromName($name)));
171+
throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no anonymous component founded', $name, implode(', ', array_keys($this->components->getProvidedServices()))));
167172
}
168173

169174
return $this->components->get($name);
@@ -205,12 +210,31 @@ private function postMount(object $component, array $data): array
205210

206211
private function isStaticComponent(string $name): bool
207212
{
208-
return $this->environment->getLoader()->exists($this->getTemplateFromName($name));
213+
return null !== $this->findStaticComponentTemplate($name);
209214
}
210215

211-
private function getTemplateFromName(string $name): string
216+
public function findStaticComponentTemplate(string $name): ?string
212217
{
213-
return str_replace('.', '/', $name).$this->twigExtension;
218+
$loader = $this->environment->getLoader();
219+
$componentPath = rtrim(str_replace(':', '/', $name));
220+
221+
if ($loader->exists($componentPath)) {
222+
return $componentPath;
223+
}
224+
225+
if ($loader->exists($componentPath.'.html.twig')) {
226+
return $componentPath.'.html.twig';
227+
}
228+
229+
if ($loader->exists('components/'.$componentPath)) {
230+
return 'components/'.$componentPath;
231+
}
232+
233+
if ($loader->exists('/components/'.$componentPath.'.html.twig')) {
234+
return '/components/'.$componentPath.'.html.twig';
235+
}
236+
237+
return null;
214238
}
215239

216240
/**

src/TwigComponent/src/ComponentRenderer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ private function preRender(MountedComponent $mounted, array $context = []): PreR
105105

106106
// expose public properties and properties marked with ExposeInTemplate attribute
107107
iterator_to_array($this->exposedVariables($component, $metadata->isPublicPropsExposed())),
108+
$component instanceof StaticComponent ? $component->getProps() : []
108109
);
109110
$event = new PreRenderEvent($mounted, $metadata, $variables);
110111

src/TwigComponent/src/DependencyInjection/Compiler/TwigComponentPass.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ public function process(ContainerBuilder $container): void
5858
}
5959

6060
$factoryDefinition = $container->findDefinition('ux.twig_component.component_factory');
61-
$factoryDefinition->setArgument(0, ServiceLocatorTagPass::register($container, $componentReferences));
62-
$factoryDefinition->setArgument(3, $componentConfig);
63-
$factoryDefinition->setArgument(4, $componentClassMap);
61+
$factoryDefinition->setArgument(1, ServiceLocatorTagPass::register($container, $componentReferences));
62+
$factoryDefinition->setArgument(4, $componentConfig);
63+
$factoryDefinition->setArgument(5, $componentClassMap);
6464
}
6565
}

src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ public function load(array $configs, ContainerBuilder $container): void
4040
throw new LogicException('The TwigBundle is not registered in your application. Try running "composer require symfony/twig-bundle".');
4141
}
4242

43-
$configuration = new Configuration();
44-
$config = $this->processConfiguration($configuration, $configs);
45-
4643
$container->registerAttributeForAutoconfiguration(
4744
AsTwigComponent::class,
4845
static function (ChildDefinition $definition, AsTwigComponent $attribute) {
@@ -52,12 +49,11 @@ static function (ChildDefinition $definition, AsTwigComponent $attribute) {
5249

5350
$container->register('ux.twig_component.component_factory', ComponentFactory::class)
5451
->setArguments([
52+
new Reference('twig'),
5553
class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %s.', TwigComponentPass::class)) : null,
5654
new Reference('property_accessor'),
5755
new Reference('event_dispatcher'),
5856
class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %s.', TwigComponentPass::class)) : [],
59-
new Reference('twig'),
60-
$config['template_extension'],
6157
])
6258
;
6359

src/TwigComponent/src/StaticComponent.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,20 @@
44

55
class StaticComponent
66
{
7+
private array $props = [];
8+
9+
public function setProps(array $props)
10+
{
11+
$this->props = $props;
12+
}
13+
14+
public function getProps(): array
15+
{
16+
return $this->props;
17+
}
18+
19+
public function mount(array $props = [])
20+
{
21+
$this->setProps($props);
22+
}
723
}

src/TwigComponent/src/Twig/ComponentExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public function getTokenParsers(): array
4949
{
5050
return [
5151
new ComponentTokenParser(fn () => $this->container->get(ComponentFactory::class)),
52+
new PropsTokenParser(),
5253
];
5354
}
5455

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Symfony\UX\TwigComponent\Twig;
4+
5+
use Twig\Compiler;
6+
use Twig\Node\Node;
7+
8+
class PropsNode extends Node
9+
{
10+
public function __construct(array $propsNames, array $values, $lineno = 0, string $tag = null)
11+
{
12+
parent::__construct($values, ['names' => $propsNames], $lineno, $tag);
13+
}
14+
15+
public function compile(Compiler $compiler)
16+
{
17+
foreach ($this->getAttribute('names') as $name) {
18+
$compiler
19+
->addDebugInfo($this)
20+
->write('if (!isset($context[\''.$name.'\'])) {')
21+
;
22+
23+
if (!$this->hasNode($name)) {
24+
$compiler
25+
->write('throw new \Exception("'.$name.' should be defined for component '.$this->getTemplateName().'");')
26+
->write('}')
27+
;
28+
29+
continue;
30+
}
31+
32+
$compiler
33+
->write('$context[\''.$name.'\'] = ')
34+
->subcompile($this->getNode($name))
35+
->raw(";\n")
36+
->write('}')
37+
;
38+
}
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Symfony\UX\TwigComponent\Twig;
4+
5+
use Twig\Token;
6+
use Twig\TokenParser\AbstractTokenParser;
7+
8+
class PropsTokenParser extends AbstractTokenParser
9+
{
10+
public function parse(Token $token)
11+
{
12+
$parser = $this->parser;
13+
$stream = $parser->getStream();
14+
15+
$names = [];
16+
$values = [];
17+
while (!$stream->nextIf(Token::BLOCK_END_TYPE)) {
18+
$name = $stream->expect(\Twig\Token::NAME_TYPE)->getValue();
19+
20+
if ($stream->nextIf(Token::OPERATOR_TYPE, '=')) {
21+
$values[$name] = $parser->getExpressionParser()->parseExpression();
22+
}
23+
24+
$names[] = $name;
25+
26+
if (!$stream->nextIf(Token::PUNCTUATION_TYPE)) {
27+
break;
28+
}
29+
}
30+
31+
$stream->expect(\Twig\Token::BLOCK_END_TYPE);
32+
33+
return new PropsNode($names, $values, $token->getLine(), $token->getValue());
34+
}
35+
36+
public function getTag()
37+
{
38+
return 'props';
39+
}
40+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<twig:Button label='Click me'/>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<twig:Button label='Click me' :primary='false'/>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{% props label, primary = true %}
2+
3+
<button class="{{ primary ? 'primary' : 'secondary' }}">
4+
{{ label }}
5+
</button>

src/TwigComponent/tests/Fixtures/templates/static/button.html.twig

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/TwigComponent/tests/Integration/ComponentExtensionTest.php

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,27 @@ public function testCanRenderEmbeddedComponent(): void
151151
$this->assertStringContainsString('custom td (1)', $output);
152152
}
153153

154-
public function testCanRenderStaticComponent(): void
154+
public function testComponentWithNamespace(): void
155155
{
156-
$output = self::getContainer()->get(Environment::class)->render('render_static_component.html.twig');
156+
$output = $this->renderComponent('foo:bar:baz');
157157

158-
$this->assertStringContainsString('I am static', $output);
158+
$this->assertStringContainsString('Content...', $output);
159159
}
160160

161-
public function testComponentWithNamespace(): void
161+
public function testRenderAnonymousComponent(): void
162162
{
163-
$output = $this->renderComponent('foo:bar:baz');
163+
$output = self::getContainer()->get(Environment::class)->render('anonymous_component.html.twig');
164164

165-
$this->assertStringContainsString('Content...', $output);
165+
$this->assertStringContainsString('Click me', $output);
166+
$this->assertStringContainsString('class="primary"', $output);
167+
}
168+
169+
public function testRenderAnonymousComponentOverwriteProps(): void
170+
{
171+
$output = self::getContainer()->get(Environment::class)->render('anonymous_component_overwrite_props.html.twig');
172+
173+
$this->assertStringContainsString('Click me', $output);
174+
$this->assertStringContainsString('class="secondary"', $output);
166175
}
167176

168177
private function renderComponent(string $name, array $data = []): string

0 commit comments

Comments
 (0)