Skip to content

Commit 843688f

Browse files
committed
mixing slots and blocks
1 parent 4f95295 commit 843688f

File tree

10 files changed

+282
-110
lines changed

10 files changed

+282
-110
lines changed

src/TwigComponent/src/Twig/AttributeBag.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,17 @@ public function __toString(): string
164164
$value = $key;
165165
}
166166

167+
if (\is_array($value)) {
168+
$convertedArray = '[';
169+
foreach ($value as $key => $item) {
170+
$convertedArray .= $key.'=>'.$item.',';
171+
}
172+
173+
$convertedArray = rtrim($convertedArray, ',');
174+
$convertedArray .= ']';
175+
$value = $convertedArray;
176+
}
177+
167178
$string .= ' '.$key.'="'.str_replace('"', '\\"', trim($value)).'"';
168179
}
169180

src/TwigComponent/src/Twig/ComponentSlot.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ public function withAttributes(array $attributes): self
3939
return $this;
4040
}
4141

42+
public function withContext(array $contexts): void
43+
{
44+
$content = $this->contents;
45+
46+
foreach ($contexts as $key => $value) {
47+
$content = str_replace("<slot_value name=\"$key\"/>", $value, $content);
48+
}
49+
50+
$this->contents = $content;
51+
}
52+
4253
public function toHtml(): string
4354
{
4455
return $this->contents;

src/TwigComponent/src/Twig/TwigComponentNode.php

Lines changed: 15 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,10 @@
1111

1212
namespace Symfony\UX\TwigComponent\Twig;
1313

14-
use Symfony\UX\TwigComponent\ComponentFactory;
1514
use Symfony\UX\TwigComponent\ComponentMetadata;
1615
use Twig\Compiler;
17-
use Twig\Environment;
16+
use Twig\Node\EmbedNode;
1817
use Twig\Node\Expression\AbstractExpression;
19-
use Twig\Node\Expression\ConstantExpression;
20-
use Twig\Node\IncludeNode;
2118
use Twig\Node\Node;
2219

2320
/**
@@ -28,33 +25,28 @@
2825
*
2926
* @internal
3027
*/
31-
class TwigComponentNode extends IncludeNode
28+
class TwigComponentNode extends EmbedNode
3229
{
33-
private Environment $environment;
34-
35-
/**
36-
* @param callable():ComponentFactory $factory
37-
*/
38-
public function __construct(string $componentName, Node $slot, ?AbstractExpression $variables, int $lineno, callable $factory, Environment $environment)
30+
public function __construct(string $componentName, string $template, ?AbstractExpression $variables, int $lineno, $index, $tag, bool $only, Node $slot, ?ComponentMetadata $componentMetadata)
3931
{
40-
parent::__construct(new ConstantExpression('not_used', $lineno), $variables, false, false, $lineno, null);
41-
$this->setAttribute('componentName', $componentName);
42-
$this->setAttribute('componentMetadata', $factory()->metadataForTwigComponent($componentName));
32+
parent::__construct($template, $index, $variables, $only, false, $lineno, $tag);
33+
34+
$this->setAttribute('component', $componentName);
35+
$this->setAttribute('componentMetadata', $componentMetadata);
36+
4337
$this->setNode('slot', $slot);
44-
$this->environment = $environment;
4538
}
4639

4740
public function compile(Compiler $compiler): void
4841
{
4942
$compiler->addDebugInfo($this);
5043

5144
$template = $compiler->getVarName();
52-
5345
$compiler->write(sprintf('$%s = ', $template));
54-
5546
$this->addGetTemplate($compiler);
5647

5748
$compiler
49+
->raw(';')
5850
->write(sprintf("if ($%s) {\n", $template))
5951
->write('$slotsStack = $slotsStack ?? [];'.\PHP_EOL)
6052
->write('$slotsStack[] = $slots ?? [];'.\PHP_EOL)
@@ -69,32 +61,13 @@ public function compile(Compiler $compiler): void
6961
->write('ob_start();'.\PHP_EOL)
7062
->subcompile($this->getNode('slot'))
7163
->write('$slot = ob_get_clean();'.\PHP_EOL)
72-
->write(sprintf('$%s->display(', $template));
73-
74-
$this->addTemplateArguments($compiler);
75-
76-
$compiler
77-
->raw(");\n")
78-
->write('$slots = array_pop($slotsStack);'.\PHP_EOL)
79-
->write("}\n")
8064
;
81-
}
8265

83-
protected function addGetTemplate(Compiler $compiler)
84-
{
85-
$compiler
86-
->raw('$this->loadTemplate('.\PHP_EOL)
87-
->indent(1)
88-
->write('')
89-
->repr($this->getTemplatePath())
90-
->raw(', '.\PHP_EOL)
91-
->write('')
92-
->repr($this->getTemplatePath())
93-
->raw(', '.\PHP_EOL)
94-
->write('')
95-
->repr($this->getTemplateLine())
96-
->indent(-1)
97-
->raw(\PHP_EOL.');'.\PHP_EOL.\PHP_EOL);
66+
$compiler->raw(sprintf('$%s->display(', $template));
67+
68+
$this->addTemplateArguments($compiler);
69+
$compiler->raw(");\n");
70+
$compiler->write("}\n");
9871
}
9972

10073
protected function addTemplateArguments(Compiler $compiler)
@@ -134,44 +107,13 @@ protected function addTemplateArguments(Compiler $compiler)
134107
$compiler->write(")\n");
135108
}
136109

137-
private function getTemplatePath(): string
138-
{
139-
$name = $this->getAttribute('componentName');
140-
141-
$loader = $this->environment->getLoader();
142-
$componentPath = rtrim(str_replace('.', '/', $name));
143-
144-
/** @var ComponentMetadata $componentMetadata */
145-
if (($componentMetadata = $this->getAttribute('componentMetadata')) !== null) {
146-
return $componentMetadata->getTemplate();
147-
}
148-
149-
if ($loader->exists($componentPath)) {
150-
return $componentPath;
151-
}
152-
153-
if ($loader->exists($componentPath.'.html.twig')) {
154-
return $componentPath.'.html.twig';
155-
}
156-
157-
if ($loader->exists('components/'.$componentPath)) {
158-
return 'components/'.$componentPath;
159-
}
160-
161-
if ($loader->exists('/components/'.$componentPath.'.html.twig')) {
162-
return '/components/'.$componentPath.'.html.twig';
163-
}
164-
165-
throw new \LogicException("No template found for: {$name}");
166-
}
167-
168110
private function addComponentProps(Compiler $compiler)
169111
{
170112
$compiler
171113
->raw('$props = $this->extensions[')
172114
->string(ComponentExtension::class)
173115
->raw(']->embeddedContext(')
174-
->string($this->getAttribute('componentName'))
116+
->string($this->getAttribute('component'))
175117
->raw(', ')
176118
;
177119

src/TwigComponent/src/Twig/TwigComponentTokenParser.php

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
use Symfony\UX\TwigComponent\ComponentFactory;
1515
use Twig\Environment;
1616
use Twig\Node\Expression\AbstractExpression;
17+
use Twig\Node\Expression\ArrayExpression;
1718
use Twig\Node\Expression\ConstantExpression;
1819
use Twig\Node\Expression\NameExpression;
20+
use Twig\Node\ModuleNode;
1921
use Twig\Node\Node;
2022
use Twig\Token;
2123
use Twig\TokenParser\AbstractTokenParser;
@@ -35,8 +37,11 @@ final class TwigComponentTokenParser extends AbstractTokenParser
3537

3638
private Environment $environment;
3739

40+
/**
41+
* @param callable():ComponentFactory $factory
42+
*/
3843
public function __construct(
39-
$factory,
44+
callable $factory,
4045
Environment $environment
4146
) {
4247
$this->factory = $factory;
@@ -45,25 +50,58 @@ public function __construct(
4550

4651
public function parse(Token $token): Node
4752
{
53+
$stream = $this->parser->getStream();
4854
$parent = $this->parser->getExpressionParser()->parseExpression();
49-
$name = $this->componentName($parent);
55+
$componentName = $this->componentName($parent);
56+
$componentMetadata = $this->factory()->metadataForTwigComponent($componentName);
57+
5058
[$variables, $only] = $this->parseArguments();
51-
$slot = $this->parser->subparse([$this, 'decideBlockEnd'], true);
52-
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
5359

54-
return new TwigComponentNode($name, $slot, $variables, $token->getLine(), $this->factory, $this->environment);
60+
if (null === $variables) {
61+
$variables = new ArrayExpression([], $parent->getTemplateLine());
62+
}
63+
64+
$parentToken = new Token(Token::STRING_TYPE, $this->getTemplatePath($componentName), $token->getLine());
65+
$fakeParentToken = new Token(Token::STRING_TYPE, '__parent__', $token->getLine());
66+
67+
// inject a fake parent to make the parent() function work
68+
$stream->injectTokens([
69+
new Token(Token::BLOCK_START_TYPE, '', $token->getLine()),
70+
new Token(Token::NAME_TYPE, 'extends', $token->getLine()),
71+
$parentToken,
72+
new Token(Token::BLOCK_END_TYPE, '', $token->getLine()),
73+
]);
74+
75+
$module = $this->parser->parse($stream, fn (Token $token) => $token->test("end_{$this->getTag()}"), true);
76+
77+
$slot = $this->getSlotFromBlockContent($module);
78+
79+
// override the parent with the correct one
80+
if ($fakeParentToken === $parentToken) {
81+
$module->setNode('parent', $parent);
82+
}
83+
84+
$this->parser->embedTemplate($module);
85+
86+
$stream->expect(Token::BLOCK_END_TYPE);
87+
88+
return new TwigComponentNode(
89+
$componentName,
90+
$module->getTemplateName(),
91+
$variables, $token->getLine(),
92+
$module->getAttribute('index'),
93+
$this->getTag(),
94+
$only,
95+
$slot,
96+
$componentMetadata
97+
);
5598
}
5699

57100
public function getTag(): string
58101
{
59102
return 'twig_component';
60103
}
61104

62-
public function decideBlockEnd(Token $token): bool
63-
{
64-
return $token->test('end_twig_component');
65-
}
66-
67105
private function componentName(AbstractExpression $expression): string
68106
{
69107
if ($expression instanceof ConstantExpression) { // using {% component 'name' %}
@@ -74,7 +112,16 @@ private function componentName(AbstractExpression $expression): string
74112
return $expression->getAttribute('name');
75113
}
76114

77-
throw new \LogicException('Could not parse twig component name.');
115+
throw new \LogicException('Could not parse component name.');
116+
}
117+
118+
private function factory(): ComponentFactory
119+
{
120+
if (\is_callable($this->factory)) {
121+
$this->factory = ($this->factory)();
122+
}
123+
124+
return $this->factory;
78125
}
79126

80127
private function parseArguments(): array
@@ -97,4 +144,41 @@ private function parseArguments(): array
97144

98145
return [$variables, $only];
99146
}
147+
148+
private function getTemplatePath(string $name): string
149+
{
150+
$loader = $this->environment->getLoader();
151+
$componentPath = rtrim(str_replace('.', '/', $name));
152+
153+
if (($componentMetadata = $this->factory->metadataForTwigComponent($name)) !== null) {
154+
return $componentMetadata->getTemplate();
155+
}
156+
157+
if ($loader->exists($componentPath)) {
158+
return $componentPath;
159+
}
160+
161+
if ($loader->exists($componentPath.'.html.twig')) {
162+
return $componentPath.'.html.twig';
163+
}
164+
165+
if ($loader->exists('components/'.$componentPath)) {
166+
return 'components/'.$componentPath;
167+
}
168+
169+
if ($loader->exists('/components/'.$componentPath.'.html.twig')) {
170+
return '/components/'.$componentPath.'.html.twig';
171+
}
172+
173+
throw new \LogicException("No template found for: {$name}");
174+
}
175+
176+
private function getSlotFromBlockContent(ModuleNode $module): Node
177+
{
178+
if ($module->getNode('blocks')->hasNode('content')) {
179+
return $module->getNode('blocks')->getNode('content')->getNode(0)->getNode('body');
180+
}
181+
182+
return new Node();
183+
}
100184
}

0 commit comments

Comments
 (0)