Skip to content

Commit 63a4b5c

Browse files
author
matheo
committed
add syntaxt for slot
1 parent 96b7c6f commit 63a4b5c

File tree

7 files changed

+76
-57
lines changed

7 files changed

+76
-57
lines changed

src/TwigComponent/src/Twig/ComponentLexer.php

Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88

99
class ComponentLexer extends Lexer
1010
{
11-
const ATTRIBUTES_REGEX = '(?<attributes>(?:\s+[\w\-:.@]+(=(?:\\\"[^\\\"]*\\\"|\'[^\']*\'|[^\'\\\"=<>]+))?)*\s*)';
12-
const COMPONENTS_REGEX = [
13-
'open_tags' => '/<\s*([A-Z][\w\-\:\.]+)\s*' . self::ATTRIBUTES_REGEX . '(\s?)+>/',
14-
'close_tags' => '/<\/\s*([A-Z][\w\-\:\.]+)\s*>/',
15-
'self_close_tags' => '/<\s*([A-Z][\w\-\:\.]+)\s*' . self::ATTRIBUTES_REGEX . '(\s?)+\/>/',
16-
];
11+
public const ATTRIBUTES_REGEX = '(?<attributes>(?:\s+[\w\-:.@]+(=(?:\\\"[^\\\"]*\\\"|\'[^\']*\'|[^\'\\\"=<>]+))?)*\s*)';
12+
public const OPEN_TAGS_REGEX = '/<\s*x-(?<name>([[\w\-\:\.]+))\s*'.self::ATTRIBUTES_REGEX.'(\s?)+>/';
13+
public const CLOSE_TAGS_REGEX = '/<\/\s*x-([\w\-\:\.]+)\s*>/';
14+
public const SELF_CLOSE_TAGS_REGEX = '/<\s*x-(?<name>([\w\-\:\.]+))\s*'.self::ATTRIBUTES_REGEX.'(\s?)+\/>/';
15+
public const BLOCK_TAGS_OPEN = '/<\s*x-block\s+name=("|\')(?<name>([\w\-\:\.]+))("|\')\s*>/';
16+
public const BLOCK_TAGS_CLOSE = '/<\s*\/\s*x-block\s*>/';
17+
public const ATTRIBUTE_BAG_REGEX = '/(?:^|\s+)\{\{\s*(attributes(?:.+?(?<!\s))?)\s*\}\}/x';
18+
public const ATTRIBUTE_KEY_VALUE_REGEX = '/(?<attribute>[\w\-:.@]+)(=(?<value>(\"[^\"]+\"|\\\'[^\\\']+\\\'|[^\s>]+)))?/x';
1719

1820
public function tokenize(Source $source): TokenStream
1921
{
@@ -30,6 +32,8 @@ public function tokenize(Source $source): TokenStream
3032

3133
private function preparsed(string $value)
3234
{
35+
$value = $this->lexBlockTags($value);
36+
$value = $this->lexBlockTagsClose($value);
3337
$value = $this->lexSelfCloseTag($value);
3438
$value = $this->lexOpeningTags($value);
3539
$value = $this->lexClosingTag($value);
@@ -40,81 +44,83 @@ private function preparsed(string $value)
4044
private function lexOpeningTags(string $value)
4145
{
4246
return preg_replace_callback(
43-
self::COMPONENTS_REGEX['open_tags'],
47+
self::OPEN_TAGS_REGEX,
4448
function (array $matches) {
45-
$name = lcfirst($matches[1]);
49+
$name = $matches['name'];
4650
$attributes = $this->getAttributesFromAttributeString($matches['attributes']);
4751

48-
return "{% component " . $name . " with " . $attributes . "%}";
52+
return '{% component '.$name.' with '.$attributes.'%}';
4953
},
5054
$value
51-
5255
);
5356
}
5457

5558
private function lexClosingTag(string $value)
5659
{
57-
return preg_replace(self::COMPONENTS_REGEX['close_tags'], '{% endcomponent %}', $value);
60+
return preg_replace(self::CLOSE_TAGS_REGEX, '{% endcomponent %}', $value);
5861
}
5962

6063
private function lexSelfCloseTag(string $value)
6164
{
6265
return preg_replace_callback(
63-
self::COMPONENTS_REGEX['self_close_tags'],
66+
self::SELF_CLOSE_TAGS_REGEX,
6467
function (array $matches) {
65-
$name = lcfirst($matches[1]);
68+
$name = $matches['name'];
6669
$attributes = $this->getAttributesFromAttributeString($matches['attributes']);
6770

68-
return "{{ component('" . $name . "', " . $attributes . ") }}";
71+
return "{{ component('".$name."', ".$attributes.') }}';
72+
},
73+
$value
74+
);
75+
}
76+
77+
private function lexBlockTags(string $value)
78+
{
79+
return preg_replace_callback(
80+
self::BLOCK_TAGS_OPEN,
81+
function (array $matches) {
82+
$name = $matches['name'];
83+
84+
return '{% block '.$name.' %}';
6985
},
7086
$value
7187
);
7288
}
7389

90+
private function lexBlockTagsClose(string $value)
91+
{
92+
return preg_replace(
93+
self::BLOCK_TAGS_CLOSE,
94+
'{% endblock %}',
95+
$value
96+
);
97+
}
98+
7499
protected function getAttributesFromAttributeString(string $attributeString)
75100
{
76101
$attributeString = $this->parseAttributeBag($attributeString);
77102

78-
$pattern = '/
79-
(?<attribute>[\w\-:.@]+)
80-
(
81-
=
82-
(?<value>
83-
(
84-
\"[^\"]+\"
85-
|
86-
\\\'[^\\\']+\\\'
87-
|
88-
[^\s>]+
89-
)
90-
)
91-
)?
92-
/x';
93-
94-
if (! preg_match_all($pattern, $attributeString, $matches, PREG_SET_ORDER)) {
103+
if (!preg_match_all(self::ATTRIBUTE_KEY_VALUE_REGEX, $attributeString, $matches, \PREG_SET_ORDER)) {
95104
return '{}';
96105
}
97106

98-
99107
$attributes = [];
100-
101108
foreach ($matches as $match) {
102109
$attribute = $match['attribute'];
103110
$value = $match['value'] ?? null;
104111

105-
if (is_null($value)) {
112+
if (null === $value) {
106113
$value = 'true';
107114
}
108115

109-
110-
if (strpos($attribute, ":") === 0) {
111-
$attribute = str_replace(":", "", $attribute);
116+
if (str_starts_with($attribute, ':')) {
117+
$attribute = str_replace(':', '', $attribute);
112118
$value = $this->stripQuotes($value);
113119
}
114120

115121
$valueWithoutQuotes = $this->stripQuotes($value);
116122

117-
if ((strpos($valueWithoutQuotes, '{{') === 0) && (strpos($valueWithoutQuotes, '}}') === strlen($valueWithoutQuotes) - 2)) {
123+
if (str_starts_with($valueWithoutQuotes, '{{') && (strpos($valueWithoutQuotes, '}}') === \strlen($valueWithoutQuotes) - 2)) {
118124
$value = substr($valueWithoutQuotes, 2, -2);
119125
} else {
120126
$value = $value;
@@ -123,29 +129,24 @@ protected function getAttributesFromAttributeString(string $attributeString)
123129
$attributes[$attribute] = $value;
124130
}
125131

126-
$out = "{";
132+
$out = '{';
127133
foreach ($attributes as $key => $value) {
128134
$key = "'$key'";
129135
$out .= "$key: $value,";
130-
};
136+
}
131137

132-
return rtrim($out, ',') . "}";
138+
return rtrim($out, ',').'}';
133139
}
134140

135141
public function stripQuotes(string $value)
136142
{
137-
return strpos($value, '"') === 0 || strpos($value, '\'') === 0
143+
return str_starts_with($value, '"') || str_starts_with($value, '\'')
138144
? substr($value, 1, -1)
139145
: $value;
140146
}
141147

142148
protected function parseAttributeBag(string $attributeString)
143149
{
144-
$pattern = "/
145-
(?:^|\s+) # start of the string or whitespace between attributes
146-
\{\{\s*(attributes(?:.+?(?<!\s))?)\s*\}\} # exact match of attributes variable being echoed
147-
/x";
148-
149-
return preg_replace($pattern, ' :attributes="$1"', $attributeString);
150+
return preg_replace(self::ATTRIBUTE_BAG_REGEX, ' :attributes="$1"', $attributeString);
150151
}
151-
}
152+
}

src/TwigComponent/src/Twig/TwigEnvironmentConfigurator.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ class TwigEnvironmentConfigurator
99
{
1010
public function __construct(
1111
private readonly EnvironmentConfigurator $decorated
12-
) {}
12+
) {
13+
}
1314

1415
public function configure(Environment $environment): void
1516
{
1617
$this->decorated->configure($environment);
1718

1819
$environment->setLexer(new ComponentLexer($environment));
1920
}
20-
}
21+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<x-table caption='data table' :headers='["key", "value"]' :data='[[1, 2], [3, 4]]'>
2+
<x-block name="th">custom th ({{ parent() }})</x-block>
3+
<x-block name="td">custom td ({{ parent() }})</x-block>
4+
5+
<x-block name="footer">
6+
My footer
7+
</x-block>
8+
</x-table>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
<Component_a propA propB='hello'>
2-
</Component_a>
1+
<x-component_a propA propB='hello'>
2+
</x-component_a>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<x-component_a propA propB='hello'/>

src/TwigComponent/tests/Fixtures/templates/tags/self_closing_tag.html.twig

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

src/TwigComponent/tests/Integration/ComponentLexerTest.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,20 @@ public function testComponentSyntaxOpenTags(): void
1515
$this->assertStringContainsString('propB: hello', $output);
1616
}
1717

18-
public function testComponentSyntaxSelfClosingTags(): void
18+
public function testComponentSyntaxSelfCloseTags(): void
1919
{
20-
$output = self::getContainer()->get(Environment::class)->render('tags/self_closing_tag.html.twig');
20+
$output = self::getContainer()->get(Environment::class)->render('tags/self_close_tag.html.twig');
2121

2222
$this->assertStringContainsString('propA: 1', $output);
2323
$this->assertStringContainsString('propB: hello', $output);
2424
}
25-
}
25+
26+
public function testComponentSyntaxCanRenderEmbeddedComponent(): void
27+
{
28+
$output = self::getContainer()->get(Environment::class)->render('tags/embedded_component.html.twig');
29+
30+
$this->assertStringContainsString('<caption>data table</caption>', $output);
31+
$this->assertStringContainsString('custom th (key)', $output);
32+
$this->assertStringContainsString('custom td (1)', $output);
33+
}
34+
}

0 commit comments

Comments
 (0)