Template   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 159
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 68
dl 0
loc 159
ccs 71
cts 71
cp 1
rs 10
c 6
b 0
f 0
wmc 21

8 Methods

Rating   Name   Duplication   Size   Complexity  
A setCustomMethods() 0 3 1
A renderLayouts() 0 24 3
A getContent() 0 38 5
A layout() 0 3 1
A templateContext() 0 3 1
A setLayout() 0 9 2
A render() 0 14 2
A __construct() 0 22 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Conia\Boiler;
6
7
use Conia\Boiler\Exception\LookupException;
8
use Conia\Boiler\Exception\RuntimeException;
9
use ErrorException;
10
use Throwable;
11
12
/** @psalm-api */
13
class Template
14
{
15
    use RegistersMethod;
16
17
    public readonly Engine $engine;
18
    public readonly Sections $sections;
19
    protected ?LayoutValue $layout = null;
20
21
    /** @psalm-suppress PropertyNotSetInConstructor */
22
    protected CustomMethods $customMethods;
23
24
    /**
25
     * @psalm-param non-empty-string $path
26
     */
27 51
    public function __construct(
28
        public readonly string $path,
29
        ?Sections $sections = null,
30
        ?Engine $engine = null,
31
    ) {
32 51
        $this->sections = $sections ?: new Sections();
0 ignored issues
show
Bug introduced by
The property sections is declared read-only in Conia\Boiler\Template.
Loading history...
33 51
        $this->customMethods = new CustomMethods();
34
35 51
        if ($engine === null) {
36 15
            $dir = dirname($path);
37
38 15
            if (empty($dir) || empty($path)) {
39 1
                throw new LookupException('No directory given or empty path');
40
            }
41
42 14
            $this->engine = new Engine($dir);
0 ignored issues
show
Bug introduced by
The property engine is declared read-only in Conia\Boiler\Template.
Loading history...
43
44 13
            if (!is_file($path)) {
45 13
                throw new LookupException('Template not found: ' . $path);
46
            }
47
        } else {
48 38
            $this->engine = $engine;
49
        }
50
    }
51
52
    /**
53
     * @psalm-param list<class-string> $whitelist
54
     */
55 46
    public function render(array $context = [], array $whitelist = [], bool $autoescape = true): string
56
    {
57 46
        $content = $this->getContent($context, $whitelist, $autoescape);
58
59 40
        if ($this instanceof Layout) {
60 8
            return $content->content;
61
        }
62
63 40
        return $this->renderLayouts(
64 40
            $this,
65 40
            $content->templateContext,
66 40
            $whitelist,
67 40
            $content->content,
68 40
            $autoescape
69 40
        );
70
    }
71
72
    /**
73
     * Defines a layout template that will be wrapped around this instance.
74
     *
75
     * Typically it’s placed at the top of the file.
76
     */
77 13
    public function setLayout(LayoutValue $layout): void
78
    {
79 13
        if ($this->layout === null) {
80 13
            $this->layout = $layout;
81
82 13
            return;
83
        }
84
85 1
        throw new RuntimeException('Template error: layout already set');
86
    }
87
88 40
    public function layout(): ?LayoutValue
89
    {
90 40
        return $this->layout;
91
    }
92
93 36
    public function setCustomMethods(CustomMethods $customMethods): void
94
    {
95 36
        $this->customMethods = $customMethods;
96
    }
97
98
    /** @psalm-param list<class-string> $whitelist */
99 46
    protected function templateContext(array $context, array $whitelist, bool $autoescape): TemplateContext
100
    {
101 46
        return new TemplateContext($this, $context, $whitelist, $autoescape);
102
    }
103
104
    /** @psalm-param list<class-string> $whitelist */
105 46
    protected function getContent(array $context, array $whitelist, bool $autoescape): Content
106
    {
107 46
        $templateContext = $this->templateContext($context, $whitelist, $autoescape);
108
109 46
        $load = function (string $templatePath, array $context = []): void {
110
            // Hide $templatePath. Could be overwritten if $context['templatePath'] exists.
111 46
            $____template_path____ = $templatePath;
112
113 46
            extract($context);
114
115
            /** @psalm-suppress UnresolvableInclude */
116 46
            include $____template_path____;
117 46
        };
118
119
        /** @var callable */
120 46
        $load = $load->bindTo($templateContext);
121 46
        $level = ob_get_level();
122
123
        try {
124 46
            ob_start();
125
126 46
            $load(
127 46
                $this->path,
128 46
                $autoescape ?
129 44
                    $templateContext->context() :
130 46
                    $context
131 46
            );
132
133 40
            $content = ob_get_clean();
134
135 40
            return new Content($content, $templateContext);
136 6
        } catch (ErrorException $e) {
137 1
            throw new RuntimeException('Render error: ' . $e->getMessage());
138 5
        } catch (Throwable $e) {
139 5
            throw $e;
140
        } finally {
141 46
            while (ob_get_level() > $level) {
142 5
                ob_end_clean();
143
            }
144
        }
145
    }
146
147
    /** @psalm-param list<class-string> $whitelist */
148 40
    protected function renderLayouts(
149
        Template $template,
150
        TemplateContext $context,
151
        array $whitelist,
152
        string $content,
153
        bool $autoescape
154
    ): string {
155 40
        while ($layout = $template->layout()) {
156 12
            $file = $template->engine->getFile($layout->layout);
157 8
            $template = new Layout(
158 8
                $file,
159 8
                $content,
160 8
                $this->sections,
161 8
                $template->engine,
162 8
            );
163
164 8
            $layoutContext = $layout->context
165 1
                ? $context->context($layout->context)
166 7
                : $context->context();
167
168 8
            $content = $template->render($layoutContext, $whitelist, $autoescape);
169
        }
170
171 36
        return $content;
172
    }
173
}
174