Skip to content

Commit 33814af

Browse files
committed
feat(twig): allow string[] for ComponentAttribute values
1 parent b73eac8 commit 33814af

File tree

3 files changed

+60
-12
lines changed

3 files changed

+60
-12
lines changed

src/TwigComponent/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## Unreleased
4+
5+
- Allow `string[]` for component attribute values (filter non-strings).
6+
37
## 2.13.0
48

59
- [BC BREAK] Add component metadata to `PreMountEvent` and `PostMountEvent`

src/TwigComponent/src/ComponentAttributes.php

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,24 @@
2121
*/
2222
final class ComponentAttributes
2323
{
24+
private bool $normalized = false;
25+
2426
/**
25-
* @param array<string, string|bool> $attributes
27+
* @param array<string, string|bool, string[]> $attributes
2628
*/
2729
public function __construct(private array $attributes)
2830
{
2931
}
3032

3133
public function __toString(): string
3234
{
35+
$this->ensureNormalized();
36+
3337
return array_reduce(
3438
array_keys($this->attributes),
3539
function (string $carry, string $key) {
3640
$value = $this->attributes[$key];
3741

38-
if (!\is_scalar($value) && null !== $value) {
39-
throw new \LogicException(sprintf('A "%s" prop was passed when creating the component. No matching "%s" property or mount() argument was found, so we attempted to use this as an HTML attribute. But, the value is not a scalar (it\'s a %s). Did you mean to pass this to your component or is there a typo on its name?', $key, $key, get_debug_type($value)));
40-
}
41-
42-
if (null === $value) {
43-
trigger_deprecation('symfony/ux-twig-component', '2.8.0', 'Passing "null" as an attribute value is deprecated and will throw an exception in 3.0.');
44-
$value = true;
45-
}
46-
4742
return match ($value) {
4843
true => "{$carry} {$key}",
4944
false => $carry,
@@ -59,7 +54,7 @@ function (string $carry, string $key) {
5954
*/
6055
public function all(): array
6156
{
62-
return $this->attributes;
57+
return $this->ensureNormalized()->attributes;
6358
}
6459

6560
/**
@@ -79,6 +74,8 @@ public function defaults(iterable $attributes): self
7974
$attributes = iterator_to_array($attributes);
8075
}
8176

77+
self::normalize($attributes);
78+
8279
foreach ($this->attributes as $key => $value) {
8380
if (\in_array($key, ['class', 'data-controller', 'data-action'], true) && isset($attributes[$key])) {
8481
$attributes[$key] = "{$attributes[$key]} {$value}";
@@ -157,4 +154,34 @@ public function remove($key): self
157154

158155
return new self($attributes);
159156
}
157+
158+
private function ensureNormalized(): self
159+
{
160+
if ($this->normalized) {
161+
return $this;
162+
}
163+
164+
self::normalize($this->attributes);
165+
$this->normalized = true;
166+
167+
return $this;
168+
}
169+
170+
private static function normalize(array &$attributes): void
171+
{
172+
foreach ($attributes as $key => &$value) {
173+
if (null === $value) {
174+
trigger_deprecation('symfony/ux-twig-component', '2.8.0', 'Passing "null" as an attribute value is deprecated and will throw an exception in 3.0.');
175+
$value = true;
176+
}
177+
178+
if (\is_array($value) && array_is_list($value)) {
179+
$value = implode(' ', array_filter($value, 'is_string'));
180+
}
181+
182+
if (!\is_scalar($value)) {
183+
throw new \LogicException(sprintf('A "%s" prop was passed when creating the component. No matching "%s" property or mount() argument was found, so we attempted to use this as an HTML attribute. But, the value is not a scalar (it\'s a %s). Did you mean to pass this to your component or is there a typo on its name?', $key, $key, get_debug_type($value)));
184+
}
185+
}
186+
}
160187
}

src/TwigComponent/tests/Unit/ComponentAttributesTest.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,24 @@ public function testNullBehaviour(): void
188188
{
189189
$attributes = new ComponentAttributes(['disabled' => null]);
190190

191-
$this->assertSame(['disabled' => null], $attributes->all());
191+
$this->assertSame(['disabled' => true], $attributes->all());
192192
$this->assertSame(' disabled', (string) $attributes);
193193
}
194+
195+
public function testCannotPassNonScalarValues(): void
196+
{
197+
$this->expectException(\LogicException::class);
198+
199+
(string) new ComponentAttributes(['data-foo' => new \stdClass()]);
200+
}
201+
202+
public function testListAttributeValues(): void
203+
{
204+
$attributes = new ComponentAttributes([
205+
'style' => ['foo', null, false, true, new \stdClass(), 'bar'],
206+
]);
207+
208+
$this->assertSame(' style="foo bar"', (string) $attributes);
209+
$this->assertSame(['style' => 'foo bar'], $attributes->all());
210+
}
194211
}

0 commit comments

Comments
 (0)