• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tempestphp / tempest-framework / 14393323144

10 Apr 2025 04:27PM UTC coverage: 81.196% (-0.2%) from 81.363%
14393323144

push

github

web-flow
refactor(core): rename `DoNotDiscover` to `SkipDiscovery` (#1142)

7 of 9 new or added lines in 9 files covered. (77.78%)

52 existing lines in 11 files now uncovered.

11529 of 14199 relevant lines covered (81.2%)

106.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.7
/src/Tempest/View/src/Elements/ViewComponentElement.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\View\Elements;
6

7
use Dom\HTMLDocument;
8
use Tempest\Core\Environment;
9
use Tempest\View\Element;
10
use Tempest\View\Parser\TempestViewCompiler;
11
use Tempest\View\Parser\TempestViewParser;
12
use Tempest\View\Slot;
13
use Tempest\View\ViewComponent;
14

15
use function Tempest\Support\arr;
16
use function Tempest\Support\str;
17

18
use const Dom\HTML_NO_DEFAULT_NS;
19

20
final class ViewComponentElement implements Element
21
{
22
    use IsElement;
23

24
    private array $dataAttributes;
25

26
    public function __construct(
74✔
27
        private readonly Environment $environment,
28
        private readonly TempestViewCompiler $compiler,
29
        private readonly ElementFactory $elementFactory,
30
        private readonly ViewComponent $viewComponent,
31
        array $attributes,
32
    ) {
33
        $this->attributes = $attributes;
74✔
34
        $this->dataAttributes = arr($attributes)
74✔
35
            ->filter(fn ($_, $key) => ! str_starts_with($key, ':'))
74✔
36
            // Attributes are converted to camelCase by default for PHP variable usage, but in the context of data attributes, kebab case is good
74✔
37
            ->mapWithKeys(fn ($value, $key) => yield str($key)->kebab()->toString() => $value)
74✔
38
            ->toArray();
74✔
39
    }
40

41
    public function getViewComponent(): ViewComponent
×
42
    {
UNCOV
43
        return $this->viewComponent;
×
44
    }
45

46
    /** @return Element[] */
47
    public function getSlots(): array
73✔
48
    {
49
        $slots = [];
73✔
50

51
        foreach ($this->getChildren() as $child) {
73✔
52
            if (! ($child instanceof SlotElement)) {
34✔
53
                continue;
30✔
54
            }
55

56
            $slots[] = $child;
8✔
57
        }
58

59
        return $slots;
73✔
60
    }
61

62
    public function getSlot(string $name = 'slot'): ?Element
37✔
63
    {
64
        foreach ($this->getChildren() as $child) {
37✔
65
            if (! ($child instanceof SlotElement)) {
33✔
66
                continue;
30✔
67
            }
68

69
            if ($child->matches($name)) {
7✔
70
                return $child;
5✔
71
            }
72
        }
73

74
        if ($name === 'slot') {
34✔
75
            $elements = [];
32✔
76

77
            foreach ($this->getChildren() as $child) {
32✔
78
                if ($child instanceof SlotElement) {
30✔
79
                    continue;
4✔
80
                }
81

82
                $elements[] = $child;
30✔
83
            }
84

85
            return new CollectionElement($elements);
32✔
86
        }
87

88
        return null;
2✔
89
    }
90

91
    public function compile(): string
73✔
92
    {
93
        /** @var Slot[] $slots */
94
        $slots = arr($this->getSlots())
73✔
95
            ->mapWithKeys(fn (SlotElement $element) => yield $element->name => Slot::fromElement($element))
73✔
96
            ->toArray();
73✔
97

98
        $compiled = str($this->viewComponent->compile($this))
73✔
99
            // Fallthrough attributes
73✔
100
            ->replaceRegex(
73✔
101
                regex: '/^<(?<tag>[\w-]+)(.*?["\s])?>/', // Match the very first opening tag, this will never fail.
73✔
102
                replace: function ($matches) {
73✔
103
                    $closingTag = '</' . $matches['tag'] . '>';
38✔
104

105
                    $ast = TempestViewParser::ast($matches[0] . $closingTag);
38✔
106

107
                    $element = $this->elementFactory->make($ast[0]);
38✔
108

109
                    foreach (['class', 'style', 'id'] as $attributeName) {
38✔
110
                        if (! isset($this->dataAttributes[$attributeName])) {
38✔
111
                            continue;
36✔
112
                        }
113

114
                        if ($attributeName === 'id') {
3✔
115
                            $value = $this->dataAttributes[$attributeName];
2✔
116
                        } else {
117
                            $value = arr([
3✔
118
                                $element->getAttribute($attributeName),
3✔
119
                                $this->dataAttributes[$attributeName],
3✔
120
                            ])
3✔
121
                                ->filter()
3✔
122
                                ->implode(' ')
3✔
123
                                ->toString();
3✔
124
                        }
125

126
                        $element->setAttribute(
3✔
127
                            name: $attributeName,
3✔
128
                            value: $value,
3✔
129
                        );
3✔
130
                    }
131

132
                    return str($element->compile())->replaceLast($closingTag, '');
38✔
133
                },
73✔
134
            )
73✔
135
            ->prepend(
73✔
136
                // Add attributes to the current scope
137
                '<?php $_previousAttributes = $attributes ?? null; ?>',
73✔
138
                sprintf('<?php $attributes = \Tempest\Support\arr(%s); ?>', var_export($this->dataAttributes, true)), // @mago-expect best-practices/no-debug-symbols Set the new value of $attributes for this view component
73✔
139

140
                // Add dynamic slots to the current scope
141
                '<?php $_previousSlots = $slots ?? null; ?>', // Store previous slots in temporary variable to keep scope
73✔
142
                sprintf('<?php $slots = \Tempest\Support\arr(%s); ?>', var_export($slots, true)), // @mago-expect best-practices/no-debug-symbols Set the new value of $slots for this view component
73✔
143
            )
73✔
144
            ->append(
73✔
145
                // Restore previous slots
146
                '<?php unset($slots); ?>',
73✔
147
                '<?php $slots = $_previousSlots ?? null; ?>',
73✔
148
                '<?php unset($_previousSlots); ?>',
73✔
149

150
                // Restore previous attributes
151
                '<?php unset($attributes); ?>',
73✔
152
                '<?php $attributes = $_previousAttributes ?? null; ?>',
73✔
153
                '<?php unset($_previousAttributes); ?>',
73✔
154
            )
73✔
155
            // Compile slots
73✔
156
            ->replaceRegex(
73✔
157
                regex: '/<x-slot\s*(name="(?<name>[\w-]+)")?((\s*\/>)|><\/x-slot>)/',
73✔
158
                replace: function ($matches) {
73✔
159
                    $name = $matches['name'] ?: 'slot';
37✔
160

161
                    $slot = $this->getSlot($name);
37✔
162

163
                    if ($slot === null) {
37✔
164
                        // A slot doesn't have any content, so we'll comment it out.
165
                        // This is to prevent DOM parsing errors (slots in <head> tags is one example, see #937)
166
                        return $this->environment->isProduction() ? '' : ('<!--' . $matches[0] . '-->');
2✔
167
                    }
168

169
                    return $slot->compile();
35✔
170
                },
73✔
171
            );
73✔
172

173
        return $this->compiler->compile($compiled->toString());
73✔
174
    }
175
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc