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

tempestphp / tempest-framework / 14721086338

28 Apr 2025 11:11AM UTC coverage: 80.275% (+0.03%) from 80.248%
14721086338

push

github

web-flow
refactor(container): rename #[Lazy] to #[Proxy] (#1180)

2 of 2 new or added lines in 1 file covered. (100.0%)

7 existing lines in 4 files now uncovered.

11859 of 14773 relevant lines covered (80.27%)

106.8 hits per line

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

98.9
/src/Tempest/View/src/Parser/TempestViewCompiler.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\View\Parser;
6

7
use Tempest\Core\Kernel;
8
use Tempest\Discovery\DiscoveryLocation;
9
use Tempest\Mapper\Exceptions\ViewNotFound;
10
use Tempest\View\Attribute;
11
use Tempest\View\Attributes\AttributeFactory;
12
use Tempest\View\Components\DynamicViewComponent;
13
use Tempest\View\Element;
14
use Tempest\View\Elements\ElementFactory;
15
use Tempest\View\Elements\ViewComponentElement;
16
use Tempest\View\View;
17

18
use function Tempest\Support\arr;
19
use function Tempest\Support\path;
20
use function Tempest\Support\str;
21

22
final readonly class TempestViewCompiler
23
{
24
    public const array PHP_TOKENS = [
25
        '<?php',
26
        '<?=',
27
        '?>',
28
    ];
29

30
    public function __construct(
144✔
31
        private ElementFactory $elementFactory,
32
        private AttributeFactory $attributeFactory,
33
        private Kernel $kernel,
34
    ) {}
144✔
35

36
    public function compile(string|View $view): string
138✔
37
    {
38
        $this->elementFactory->setViewCompiler($this);
138✔
39

40
        // 1. Retrieve template
41
        $template = $this->retrieveTemplate($view);
138✔
42

43
        // 2. Parse AST
44
        $ast = $this->parseAst($template);
138✔
45

46
        // 3. Map to elements
47
        $elements = $this->mapToElements($ast);
138✔
48

49
        // 4. Apply attributes
50
        $elements = $this->applyAttributes($elements);
138✔
51

52
        // 5. Compile to PHP
53
        $compiled = $this->compileElements($elements);
132✔
54

55
        // 6. Cleanup compiled PHP
56
        $cleaned = $this->cleanupCompiled($compiled);
132✔
57

58
        return $cleaned;
132✔
59
    }
60

61
    private function retrieveTemplate(string|View $view): string
138✔
62
    {
63
        $path = ($view instanceof View) ? $view->path : $view;
138✔
64

65
        if (! str_ends_with($path, '.php')) {
138✔
66
            return $path;
128✔
67
        }
68

69
        $searchPathOptions = [
19✔
70
            $path,
19✔
71
        ];
19✔
72

73
        if ($view instanceof View && $view->relativeRootPath !== null) {
19✔
74
            $searchPathOptions[] = path($view->relativeRootPath, $path)->toString();
16✔
75
        }
76

77
        $searchPathOptions = [
19✔
78
            ...$searchPathOptions,
19✔
79
            ...arr($this->kernel->discoveryLocations)
19✔
80
                ->map(fn (DiscoveryLocation $discoveryLocation) => path($discoveryLocation->path, $path)->toString())
19✔
81
                ->toArray(),
19✔
82
        ];
19✔
83

84
        foreach ($searchPathOptions as $searchPath) {
19✔
85
            if (file_exists($searchPath)) {
19✔
86
                break;
19✔
87
            }
88
        }
89

90
        if (! file_exists($searchPath)) {
19✔
UNCOV
91
            throw new ViewNotFound($path);
×
92
        }
93

94
        return file_get_contents($searchPath);
19✔
95
    }
96

97
    private function parseAst(string $template): TempestViewAst
138✔
98
    {
99
        $tokens = new TempestViewLexer($template)->lex();
138✔
100

101
        return new TempestViewParser($tokens)->parse();
138✔
102
    }
103

104
    /**
105
     * @return Element[]
106
     */
107
    private function mapToElements(TempestViewAst $ast): array
138✔
108
    {
109
        $elements = [];
138✔
110

111
        foreach ($ast as $token) {
138✔
112
            $element = $this->elementFactory->make($token);
138✔
113

114
            if ($element === null) {
138✔
115
                continue;
56✔
116
            }
117

118
            $elements[] = $element;
138✔
119
        }
120

121
        return $elements;
138✔
122
    }
123

124
    /**
125
     * @param Element[] $elements
126
     * @return Element[]
127
     */
128
    private function applyAttributes(array $elements): array
138✔
129
    {
130
        $appliedElements = [];
138✔
131

132
        $previous = null;
138✔
133

134
        foreach ($elements as $element) {
138✔
135
            $isDynamicViewComponent = $element instanceof ViewComponentElement && $element->getViewComponent() instanceof DynamicViewComponent;
138✔
136

137
            if (! $isDynamicViewComponent) {
138✔
138
                $children = $this->applyAttributes($element->getChildren());
138✔
139
                $element->setChildren($children);
138✔
140
            }
141

142
            $element->setPrevious($previous);
138✔
143

144
            foreach ($element->getAttributes() as $name => $value) {
138✔
145
                if ($isDynamicViewComponent && $name !== ':is' && $name !== 'is') {
112✔
146
                    continue;
3✔
147
                }
148

149
                // TODO: possibly refactor attribute construction to ElementFactory?
150
                if ($value instanceof Attribute) {
112✔
151
                    $attribute = $value;
1✔
152
                } else {
153
                    $attribute = $this->attributeFactory->make($name);
112✔
154
                }
155

156
                $element = $attribute->apply($element);
112✔
157

158
                if ($element === null) {
107✔
159
                    break;
13✔
160
                }
161
            }
162

163
            if ($element === null) {
138✔
164
                continue;
13✔
165
            }
166

167
            $appliedElements[] = $element;
138✔
168

169
            $previous = $element;
138✔
170
        }
171

172
        return $appliedElements;
138✔
173
    }
174

175
    /** @param \Tempest\View\Element[] $elements */
176
    private function compileElements(array $elements): string
132✔
177
    {
178
        $compiled = arr();
132✔
179

180
        foreach ($elements as $element) {
132✔
181
            $compiled[] = $element->compile();
132✔
182
        }
183

184
        return $compiled
132✔
185
            ->implode(PHP_EOL)
132✔
186
            ->toString();
132✔
187
    }
188

189
    private function cleanupCompiled(string $compiled): string
132✔
190
    {
191
        // Remove strict type declarations
192
        $compiled = str($compiled)->replace('declare(strict_types=1);', '');
132✔
193

194
        // Cleanup and bundle imports
195
        $imports = arr();
132✔
196

197
        $compiled = $compiled->replaceRegex("/^\s*use (function )?.*;/m", function (array $matches) use (&$imports) {
132✔
198
            // The import contains escaped slashes, meaning it's a var_exported string; we can ignore those
199
            if (str_contains($matches[0], '\\\\')) {
18✔
200
                return $matches[0];
2✔
201
            }
202

203
            $imports[$matches[0]] = $matches[0];
18✔
204

205
            return '';
18✔
206
        });
132✔
207

208
        $compiled = $compiled->prepend(
132✔
209
            sprintf(
132✔
210
                '<?php
132✔
211
%s
212
?>',
132✔
213
                $imports->implode(PHP_EOL),
132✔
214
            ),
132✔
215
        );
132✔
216

217
        // Remove empty PHP blocks
218
        $compiled = $compiled->replaceRegex('/<\?php\s*\?>/', '');
132✔
219

220
        return $compiled->toString();
132✔
221
    }
222
}
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