Skip to content

Commit 5034d0a

Browse files
authored
fix(view): several bugfixes (#662)
1 parent 1cdf158 commit 5034d0a

16 files changed

+474
-30
lines changed

src/Tempest/View/src/Attributes/DataAttribute.php

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Tempest\View\Attribute;
99
use Tempest\View\Element;
1010
use Tempest\View\Elements\PhpDataElement;
11+
use Tempest\View\Elements\TextElement;
1112
use Tempest\View\Elements\ViewComponentElement;
1213

1314
final readonly class DataAttribute implements Attribute
@@ -19,19 +20,23 @@ public function __construct(
1920

2021
public function apply(Element $element): Element
2122
{
22-
if (
23-
! $element instanceof ViewComponentElement
24-
&& ! $element instanceof PhpDataElement
25-
) {
26-
return $element;
23+
$value = str($element->getAttribute($this->name));
24+
25+
// Render {{ and {!! tags
26+
if ($value->startsWith(['{{', '{!!']) && $value->endsWith(['}}', '!!}'])) {
27+
$value = (new TextElement($value->toString()))->compile();
28+
$element->setAttribute($this->name, $value);
2729
}
2830

29-
$value = str($element->getAttribute($this->name));
31+
// Data attributes should only be parsed for view components
32+
if ($element->unwrap(ViewComponentElement::class) === null) {
33+
return $element;
34+
}
3035

3136
return new PhpDataElement(
32-
$this->name,
33-
$value->toString(),
34-
$element,
37+
name: $this->name,
38+
value: $element->getAttribute($this->name),
39+
wrappingElement: $element,
3540
);
3641
}
3742
}

src/Tempest/View/src/Attributes/ElseAttribute.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
{
1414
public function apply(Element $element): ?Element
1515
{
16-
$previous = $element->getPrevious();
16+
$previous = $element->getPrevious()?->unwrap(PhpIfElement::class);
1717

1818
if (! $previous instanceof PhpIfElement) {
19-
throw new InvalidElement('There needs to be an if or elseif element before.');
19+
throw new InvalidElement('There needs to be an if or elseif element before an else element.');
2020
}
2121

2222
$previous->setElse($element);

src/Tempest/View/src/Attributes/ElseIfAttribute.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
{
1616
public function apply(Element $element): ?Element
1717
{
18-
$previous = $element->getPrevious();
18+
$previous = $element->getPrevious()?->unwrap(PhpIfElement::class);
1919

2020
if (! $previous instanceof PhpIfElement) {
21-
throw new InvalidElement('There needs to be an if or elseif element before.');
21+
throw new InvalidElement('There needs to be an if or elseif element before an elseif element.');
2222
}
2323

2424
$previous->addElseif($element);

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
namespace Tempest\View\Attributes;
66

7+
use function Tempest\Support\str;
78
use Tempest\View\Attribute;
89
use Tempest\View\Element;
910
use Tempest\View\Elements\PhpDataElement;
11+
use Tempest\View\Exceptions\InvalidExpressionAttribute;
12+
use Tempest\View\Renderers\TempestViewCompiler;
1013

1114
final readonly class ExpressionAttribute implements Attribute
1215
{
@@ -17,12 +20,18 @@ public function __construct(
1720

1821
public function apply(Element $element): Element
1922
{
23+
$value = str($element->getAttribute($this->name));
24+
25+
if ($value->startsWith(['{{', '{!!', ...TempestViewCompiler::TOKEN_MAPPING])) {
26+
throw new InvalidExpressionAttribute($value);
27+
}
28+
2029
return new PhpDataElement(
21-
$this->name,
22-
$element->getAttribute($this->name),
23-
$element->setAttribute(
24-
$this->name,
25-
sprintf('<?= %s ?>', $element->getAttribute($this->name))
30+
name: $this->name,
31+
value: $value->toString(),
32+
wrappingElement: $element->setAttribute(
33+
ltrim($this->name, ':'),
34+
sprintf('<?= %s ?>', $value),
2635
),
2736
);
2837
}

src/Tempest/View/src/Attributes/ForelseAttribute.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
{
1414
public function apply(Element $element): ?Element
1515
{
16-
$previous = $element->getPrevious();
16+
$previous = $element->getPrevious()?->unwrap(PhpForeachElement::class);
1717

1818
if (! $previous instanceof PhpForeachElement) {
19-
throw new InvalidElement('No valid foreach loop found in preceding element');
19+
throw new InvalidElement('There needs to be a foreach element before an forelse element.');
2020
}
2121

2222
$previous->setElse($element);

src/Tempest/View/src/Element.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,11 @@ public function setChildren(array $children): self;
3131

3232
/** @return \Tempest\View\Element[] */
3333
public function getChildren(): array;
34+
35+
/**
36+
* @template T of \Tempest\View\Element
37+
* @param class-string<T> $elementClass
38+
* @return T|null
39+
*/
40+
public function unwrap(string $elementClass): ?Element;
3441
}

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Tempest\View\Element;
88
use Tempest\View\View;
9+
use Tempest\View\WrapsElement;
910

1011
/** @phpstan-require-implements \Tempest\View\Element */
1112
trait IsElement
@@ -39,13 +40,23 @@ public function getAttribute(string $name): string|null
3940
{
4041
$name = ltrim($name, ':');
4142

42-
return $this->attributes[":{$name}"]
43+
if ($this instanceof WrapsElement) {
44+
$value = $this->getWrappingElement()->getAttribute($name);
45+
}
46+
47+
return
48+
$value
49+
?? $this->attributes[":{$name}"]
4350
?? $this->attributes[$name]
4451
?? null;
4552
}
4653

4754
public function setAttribute(string $name, string $value): self
4855
{
56+
if ($this instanceof WrapsElement) {
57+
$this->getWrappingElement()->setAttribute($name, $value);
58+
}
59+
4960
$this->unsetAttribute($name);
5061

5162
$this->attributes[$name] = $value;
@@ -119,4 +130,22 @@ public function setChildren(array $children): self
119130

120131
return $this;
121132
}
133+
134+
/**
135+
* @template T of \Tempest\View\Element
136+
* @param class-string<T> $elementClass
137+
* @return T|null
138+
*/
139+
public function unwrap(string $elementClass): ?Element
140+
{
141+
if ($this instanceof $elementClass) {
142+
return $this;
143+
}
144+
145+
if ($this instanceof WrapsElement) {
146+
return $this->getWrappingElement()->unwrap($elementClass);
147+
}
148+
149+
return null;
150+
}
122151
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
use function Tempest\Support\str;
88
use Tempest\View\Element;
99
use Tempest\View\Renderers\TempestViewCompiler;
10+
use Tempest\View\WrapsElement;
1011

11-
final class PhpDataElement implements Element
12+
final class PhpDataElement implements Element, WrapsElement
1213
{
1314
use IsElement;
1415

@@ -19,15 +20,16 @@ public function __construct(
1920
) {
2021
}
2122

22-
public function getAttribute(string $name): string|null
23+
public function getWrappingElement(): Element
2324
{
24-
return $this->wrappingElement->getAttribute($name);
25+
return $this->wrappingElement;
2526
}
2627

2728
public function compile(): string
2829
{
2930
$name = ltrim($this->name, ':');
3031
$isExpression = str_starts_with($this->name, ':');
32+
3133
$value = str($this->value ?? '');
3234

3335
// If the value of an attribute is PHP code, it's automatically promoted to an expression with the PHP tags stripped
@@ -40,7 +42,7 @@ public function compile(): string
4042

4143
// We'll declare the variable in PHP right before the actual element
4244
$variableDeclaration = sprintf(
43-
'$%s = %s;',
45+
'$%s ??= %s ?? null;',
4446
$name,
4547
$isExpression
4648
? $value ?: 'null'

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
use function Tempest\Support\str;
88
use Tempest\View\Element;
99
use Tempest\View\Exceptions\InvalidElement;
10+
use Tempest\View\WrapsElement;
1011

11-
final class PhpForeachElement implements Element
12+
final class PhpForeachElement implements Element, WrapsElement
1213
{
1314
use IsElement;
1415

@@ -19,6 +20,11 @@ public function __construct(
1920
) {
2021
}
2122

23+
public function getWrappingElement(): Element
24+
{
25+
return $this->wrappingElement;
26+
}
27+
2228
public function setElse(Element $element): self
2329
{
2430
if ($this->else !== null) {
@@ -54,7 +60,7 @@ public function compile(): string
5460
$this->else->consumeAttribute('forelse');
5561

5662
$compiled = sprintf(
57-
'<?php if(iterator_count(%s)): ?>
63+
'<?php if(iterator_count(%s ?? [])): ?>
5864
%s
5965
<?php else: ?>
6066
%s

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
use Tempest\View\Element;
88
use Tempest\View\Exceptions\InvalidElement;
9+
use Tempest\View\WrapsElement;
910

10-
final class PhpIfElement implements Element
11+
final class PhpIfElement implements Element, WrapsElement
1112
{
1213
use IsElement;
1314

@@ -21,6 +22,11 @@ public function __construct(
2122
) {
2223
}
2324

25+
public function getWrappingElement(): Element
26+
{
27+
return $this->wrappingElement;
28+
}
29+
2430
public function addElseif(Element $element): self
2531
{
2632
$this->elseif[] = $element;

0 commit comments

Comments
 (0)