Skip to content

Commit 80ff7be

Browse files
innocenzibrendt
andauthored
feat(view): support merging class attributes (#1020)
Co-authored-by: Brent Roose <[email protected]>
1 parent 3551008 commit 80ff7be

File tree

10 files changed

+211
-125
lines changed

10 files changed

+211
-125
lines changed

src/Tempest/Support/src/Str/ManipulatesString.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,4 +693,9 @@ public function __toString(): string
693693
{
694694
return $this->value;
695695
}
696+
697+
public static function __set_state(array $array): object
698+
{
699+
return new self($array['value']);
700+
}
696701
}

src/Tempest/View/src/Attributes/ExpressionAttribute.php

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Tempest\View\Attributes;
66

7+
use Stringable;
8+
use Tempest\Support\Arr\ArrayInterface;
79
use Tempest\View\Attribute;
810
use Tempest\View\Element;
911
use Tempest\View\Elements\PhpDataElement;
@@ -27,13 +29,48 @@ public function apply(Element $element): Element
2729
throw new InvalidExpressionAttribute($value);
2830
}
2931

30-
return new PhpDataElement(
31-
name: $this->name,
32-
value: $value->toString(),
33-
wrappingElement: $element->setAttribute(
32+
if ($this->name === ':class' || $this->name === ':style') {
33+
$value = self::resolveValue([
34+
$element->getAttribute(ltrim($this->name, ':')),
35+
sprintf('<?= %s ?>', $element->getAttribute($this->name)),
36+
]);
37+
38+
$element->setAttribute(
39+
ltrim($this->name, ':'),
40+
sprintf('%s', $value),
41+
);
42+
} else {
43+
$element->setAttribute(
3444
ltrim($this->name, ':'),
35-
sprintf('<?= %s ?>', $value),
36-
),
37-
);
45+
sprintf('<?= ' . \Tempest\View\Attributes\ExpressionAttribute::class . '::resolveValue(%s) ?>', $value),
46+
);
47+
48+
$element = new PhpDataElement(
49+
name: $this->name,
50+
value: $value->toString(),
51+
wrappingElement: $element,
52+
);
53+
}
54+
55+
$element->unsetAttribute($this->name);
56+
57+
return $element;
58+
}
59+
60+
public static function resolveValue(mixed $value): string
61+
{
62+
if ($value instanceof Stringable) {
63+
$value = (string) $value;
64+
}
65+
66+
if ($value instanceof ArrayInterface) {
67+
$value = $value->toArray();
68+
}
69+
70+
if (is_array($value)) {
71+
$value = trim(implode(' ', $value));
72+
}
73+
74+
return (string) $value;
3875
}
3976
}

src/Tempest/View/src/Element.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public function setAttribute(string $name, string $value): self;
1818

1919
public function consumeAttribute(string $name): ?string;
2020

21+
public function unsetAttribute(string $name): self;
22+
2123
public function setPrevious(?Element $previous): self;
2224

2325
public function getPrevious(): ?Element;

src/Tempest/View/src/Elements/IsElement.php

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,33 @@ trait IsElement
2424

2525
public function getAttributes(): array
2626
{
27-
return $this->attributes;
27+
if ($this instanceof WrapsElement) {
28+
$wrappingAttributes = $this->getWrappingElement()->getAttributes();
29+
} else {
30+
$wrappingAttributes = [];
31+
}
32+
33+
return [...$this->attributes, ...$wrappingAttributes];
2834
}
2935

3036
public function hasAttribute(string $name): bool
3137
{
3238
$name = ltrim($name, ':');
3339

34-
return array_key_exists(":{$name}", $this->attributes) || array_key_exists($name, $this->attributes);
40+
$attributes = $this->getAttributes();
41+
42+
return array_key_exists(":{$name}", $attributes) || array_key_exists($name, $attributes);
3543
}
3644

3745
public function getAttribute(string $name): ?string
3846
{
39-
$name = ltrim($name, ':');
47+
$attributes = $this->getAttributes();
4048

41-
if ($this instanceof WrapsElement) {
42-
$value = $this->getWrappingElement()->getAttribute($name);
43-
}
49+
$originalName = $name;
4450

45-
return $value ?? $this->attributes[":{$name}"] ?? $this->attributes[$name] ?? null;
51+
$name = ltrim($name, ':');
52+
53+
return $attributes[$originalName] ?? $this->attributes[":{$name}"] ?? $this->attributes[$name] ?? null;
4654
}
4755

4856
public function setAttribute(string $name, string $value): self
@@ -51,8 +59,6 @@ public function setAttribute(string $name, string $value): self
5159
$this->getWrappingElement()->setAttribute($name, $value);
5260
}
5361

54-
$this->unsetAttribute($name);
55-
5662
$this->attributes[$name] = $value;
5763

5864
return $this;
@@ -69,9 +75,11 @@ public function consumeAttribute(string $name): ?string
6975

7076
public function unsetAttribute(string $name): self
7177
{
72-
$name = ltrim($name, ':');
78+
if ($this instanceof WrapsElement) {
79+
$this->getWrappingElement()->unsetAttribute($name);
80+
}
7381

74-
unset($this->attributes[$name], $this->attributes[":{$name}"]);
82+
unset($this->attributes[$name]);
7583

7684
return $this;
7785
}

src/Tempest/View/src/Elements/PhpDataElement.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Tempest\View\Elements;
66

7+
use Stringable;
78
use Tempest\View\Element;
89
use Tempest\View\Renderers\TempestViewCompiler;
910
use Tempest\View\WrapsElement;
@@ -16,7 +17,7 @@ final class PhpDataElement implements Element, WrapsElement
1617

1718
public function __construct(
1819
private readonly string $name,
19-
private readonly ?string $value,
20+
private readonly null|string|array $value,
2021
private readonly Element $wrappingElement,
2122
) {
2223
}
@@ -31,15 +32,7 @@ public function compile(): string
3132
$name = ltrim($this->name, ':');
3233
$isExpression = str_starts_with($this->name, ':');
3334

34-
$value = str($this->value ?? '');
35-
36-
// If the value of an attribute is PHP code, it's automatically promoted to an expression with the PHP tags stripped
37-
if ($value->startsWith([TempestViewCompiler::TOKEN_PHP_OPEN, TempestViewCompiler::TOKEN_PHP_SHORT_ECHO])) {
38-
$value = $value->replace(TempestViewCompiler::TOKEN_MAPPING, '');
39-
$isExpression = true;
40-
}
41-
42-
$value = $value->toString();
35+
$value = $this->value ?? '';
4336

4437
// We'll declare the variable in PHP right before the actual element
4538
$variableDeclaration = sprintf(

src/Tempest/View/src/Elements/PhpForeachElement.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public function setElse(Element $element): self
3939

4040
public function compile(): string
4141
{
42-
$foreachAttribute = $this->wrappingElement->consumeAttribute('foreach');
42+
$foreachAttribute = $this->wrappingElement->consumeAttribute(':foreach');
4343

4444
$compiled = sprintf(
4545
'<?php foreach (%s): ?>
@@ -57,7 +57,7 @@ public function compile(): string
5757
if ($this->else !== null) {
5858
$collectionName = str($foreachAttribute)->match('/^(?<match>.*)\s+as/')['match'];
5959

60-
$this->else->consumeAttribute('forelse');
60+
$this->else->consumeAttribute(':forelse');
6161

6262
$compiled = sprintf(
6363
'<?php if(iterator_count(%s ?? [])): ?>

src/Tempest/View/src/Elements/PhpIfElement.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public function compile(): string
5050
$compiled = sprintf(
5151
'<?php if(%s): ?>
5252
%s',
53-
$this->wrappingElement->consumeAttribute('if'),
53+
$this->wrappingElement->consumeAttribute(':if'),
5454
$this->wrappingElement->compile(),
5555
);
5656

@@ -60,13 +60,13 @@ public function compile(): string
6060
<?php elseif(%s): ?>
6161
%s',
6262
$compiled,
63-
$elseif->consumeAttribute('elseif'),
63+
$elseif->consumeAttribute(':elseif'),
6464
$elseif->compile(),
6565
);
6666
}
6767

6868
if ($this->else !== null) {
69-
$this->else->consumeAttribute('else');
69+
$this->else->consumeAttribute(':else');
7070

7171
$compiled = sprintf(
7272
'%s

src/Tempest/View/src/Exceptions/InvalidDataAttribute.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public function __construct(string $name, string $value)
1111
{
1212
$value = str_replace(TempestViewCompiler::TOKEN_MAPPING, array_keys(TempestViewCompiler::TOKEN_MAPPING), $value);
1313

14-
$message = sprintf("An data attribute's value cannot contain a PHP expression (<?php or <?=), use expression attributes instead:
14+
$message = sprintf("A data attribute's value cannot contain a PHP expression (<?php or <?=), use expression attributes instead:
1515
× %s=\"%s\"
1616
✓ %s=\"%s\"", $name, $value, ":{$name}", trim(str_replace(array_keys(TempestViewCompiler::TOKEN_MAPPING), '', $value)));
1717

tests/Integration/Cache/CacheClearCommandTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ public function test_cache_clear_single(): void
1717
{
1818
$this->console
1919
->call('cache:clear')
20-
->print()
2120
->assertSee(ProjectCache::class)
2221
->submit('0')
2322
->submit('yes')

0 commit comments

Comments
 (0)