Skip to content

Commit

Permalink
feat(twig): Add attribute merging behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
kbond committed Jan 30, 2024
1 parent 3f7f811 commit b73e1b9
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/TwigComponent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- Make `ComponentAttributes` traversable/countable.
- Add attribute merging behaviour system.

## 2.13.0

Expand Down
42 changes: 42 additions & 0 deletions src/TwigComponent/src/ComponentAttributeValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\TwigComponent;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
final class ComponentAttributeValue implements \Stringable
{
public function __construct(private string $value, private string $separator = ' ')
{
}

public function __toString(): string
{
return $this->value;
}

public function default(string $value): string
{
return $this->value ?: $value;
}

public function prepend(string $default): string
{
return trim(sprintf('%s%s%s', $default, $this->separator, $this->value));
}

public function append(string $default): string
{
return trim(sprintf('%s%s%s', $this->value, $this->separator, $default));
}
}
35 changes: 34 additions & 1 deletion src/TwigComponent/src/ComponentAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
*/
final class ComponentAttributes implements \IteratorAggregate, \Countable
{
/** @var array<string,true> */
private array $rendered = [];

/**
* @param array<string, string|bool> $attributes
*/
Expand All @@ -31,7 +34,10 @@ public function __construct(private array $attributes)
public function __toString(): string
{
return array_reduce(
array_keys($this->attributes),
array_filter(
array_keys($this->attributes),
fn (string $key) => !isset($this->rendered[$key])
),
function (string $carry, string $key) {
$value = $this->attributes[$key];

Expand All @@ -54,6 +60,29 @@ function (string $carry, string $key) {
);
}

public function __clone(): void
{
$this->rendered = [];
}

public function __get(string $attribute): ComponentAttributeValue
{
return $this->render($attribute);
}

public function render(string $attribute, string $separator = ' '): ComponentAttributeValue
{
$value = $this->attributes[$attribute] ?? '';

if (!\is_string($value)) {
throw new \LogicException(sprintf('Cannot render attribute "%s" because it is not a string.', $attribute));
}

$this->rendered[$attribute] = true;

return new ComponentAttributeValue($value, $separator);
}

/**
* @return array<string, string|bool>
*/
Expand Down Expand Up @@ -89,6 +118,10 @@ public function defaults(iterable $attributes): self
$attributes[$key] = $value;
}

foreach (array_keys($this->rendered) as $attribute) {
unset($attributes[$attribute]);
}

return new self($attributes);
}

Expand Down
74 changes: 74 additions & 0 deletions src/TwigComponent/tests/Unit/ComponentAttributesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,78 @@ public function testIsTraversableAndCountable(): void
$this->assertSame($attributes->all(), iterator_to_array($attributes));
$this->assertCount(1, $attributes);
}

public function testRenderIndividualAttribute(): void
{
$attributes = new ComponentAttributes(['attr1' => 'value1', 'attr2' => 'value2']);

$this->assertSame('value1', (string) $attributes->render('attr1'));
$this->assertSame(' attr2="value2"', (string) $attributes);
}

public function testRenderIndividualAttributeUsingMagicGet(): void
{
$attributes = new ComponentAttributes(['attr1' => 'value1', 'attr2' => 'value2']);

$this->assertSame('value1', (string) $attributes->attr1);
$this->assertSame(' attr2="value2"', (string) $attributes);
}

public function testCannotRenderNonStringAttribute(): void
{
$attributes = new ComponentAttributes(['attr1' => false]);

$this->expectException(\LogicException::class);

$attributes->render('attr1');
}

public function testRenderedAttributeCanHaveADefault(): void
{
$attributes = new ComponentAttributes(['attr1' => 'value1', 'attr2' => 'value2']);

$this->assertSame('value1', $attributes->render('attr1')->default('default'));
$this->assertSame('default', $attributes->render('attr3')->default('default'));
$this->assertSame(' attr2="value2"', (string) $attributes);
}

public function testRenderedAttributeCanBePrepended(): void
{
$attributes = new ComponentAttributes(['attr1' => 'value1', 'attr2' => 'value2']);

$this->assertSame('default value1', $attributes->render('attr1')->prepend('default'));
$this->assertSame('default', $attributes->render('attr3')->prepend('default'));
$this->assertSame(' attr2="value2"', (string) $attributes);
}

public function testRenderedAttributeCanBeAppended(): void
{
$attributes = new ComponentAttributes(['attr1' => 'value1', 'attr2' => 'value2']);

$this->assertSame('value1 default', $attributes->render('attr1')->append('default'));
$this->assertSame('default', $attributes->render('attr3')->append('default'));
$this->assertSame(' attr2="value2"', (string) $attributes);
}

public function testDefaultsIgnoredIfAlreadyRendered(): void
{
$attributes = new ComponentAttributes(['attr1' => 'value1', 'attr2' => 'value2']);
$this->assertSame('value1', (string) $attributes->render('attr1'));

$attributes = $attributes->defaults(['attr1' => 'default']);

$this->assertSame(' attr2="value2"', (string) $attributes);
}

public function testCloningResetsRenderedAttributes(): void
{
$attributes = new ComponentAttributes(['attr1' => 'value1', 'attr2' => 'value2']);

$this->assertSame('value1', (string) $attributes->render('attr1'));
$this->assertSame(' attr2="value2"', (string) $attributes);

$attributes = clone $attributes;

$this->assertSame(' attr1="value1" attr2="value2"', (string) $attributes);
}
}

0 comments on commit b73e1b9

Please sign in to comment.