Passed
Push — main ( c4d8ca...cfd5a4 )
by Thierry
01:32
created

Context::__render()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 29
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 3
nop 1
dl 0
loc 29
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Context.php - Template context
5
 *
6
 * A context for a template being rendered.
7
 *
8
 * The "$this" var in a template will refer to an instance of this
9
 * class, which will then provide the template variables, and the
10
 * include() method, to render a template inside of another.
11
 *
12
 * @package jaxon-utils
13
 * @author Thierry Feuzeu <[email protected]>
14
 * @copyright 2022 Thierry Feuzeu <[email protected]>
15
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
16
 * @link https://github.com/jaxon-php/jaxon-core
17
 */
18
19
namespace Jaxon\Utils\Template;
20
21
use Closure;
22
23
use function call_user_func;
24
use function is_readable;
25
use function ob_get_clean;
26
use function ob_start;
27
use function strrpos;
28
use function substr;
29
use function trim;
30
31
class Context
32
{
33
    /**
34
     * @var Context|null
35
     */
36
    private $__extends__ = null;
37
38
    /**
39
     * @var string
40
     */
41
    private $__block_name__;
42
43
    /**
44
     * @var array
45
     */
46
    private $__properties__ = [];
47
48
    /**
49
     * The constructor
50
     *
51
     * @param array $__namespaces__
52
     * @param string $__default_namespace__
53
     * @param string $__template__
54
     */
55
    public function __construct(protected array $__namespaces__,
56
        protected string $__default_namespace__, protected string $__template__)
57
    {}
58
59
    /**
60
     * @param string $name
61
     *
62
     * @return mixed
63
     */
64
    public function __get(string $name)
65
    {
66
        return $this->__properties__[$name];
67
    }
68
69
    /**
70
     * @param string $name
71
     * @param mixed $value
72
     *
73
     * @return void
74
     */
75
    public function __set(string $name, $value)
76
    {
77
        $this->__properties__[$name] = $value;
78
    }
79
80
    /**
81
     * Include a template
82
     *
83
     * @param string $template The name of template to be rendered
84
     * @param array $vars The template vars
85
     *
86
     * @return void
87
     */
88
    protected function include(string $template, array $vars = [])
89
    {
90
        $context = new Context($this->__namespaces__,
91
            $this->__default_namespace__, $template);
92
        echo $context->__render($vars);
93
    }
94
95
    /**
96
     * @param string $template The name of template to be rendered
97
     *
98
     * @return void
99
     */
100
    public function extend(string $template): void
101
    {
102
        $this->__extends__ = new Context($this->__namespaces__,
103
            $this->__default_namespace__, $template);
104
    }
105
106
    /**
107
     * Start a new block
108
     *
109
     * @param string $name
110
     *
111
     * @return void
112
     */
113
    public function block(string $name)
114
    {
115
        ob_start();
116
        $this->__block_name__ = $name;
117
    }
118
119
    /**
120
     * End the current block
121
     *
122
     * @param Closure|null $filter
123
     *
124
     * @return void
125
     */
126
    public function endblock(Closure $filter = null)
127
    {
128
        $content = ob_get_clean();
129
        $this->__set($this->__block_name__, !$filter ? $content : $filter($content));
130
    }
131
132
    /**
133
     * @return string
134
     */
135
    private function __path(): string
136
    {
137
        $template = trim($this->__template__);
138
        // Get the namespace name
139
        $namespace = $this->__default_namespace__;
140
        $separatorPosition = strrpos($template, '::');
141
        if($separatorPosition !== false)
142
        {
143
            $namespace = substr($template, 0, $separatorPosition);
144
            $template = substr($template, $separatorPosition + 2);
145
        }
146
        // Check if the namespace is defined
147
        if(!isset($this->__namespaces__[$namespace]))
148
        {
149
            return $template;
150
        }
151
152
        $namespace = $this->__namespaces__[$namespace];
153
        // Get the template path
154
        return $namespace['directory'] . $template . $namespace['extension'];
155
    }
156
157
    /**
158
     * Render a template
159
     *
160
     * @param array $vars The template vars
161
     *
162
     * @return string
163
     */
164
    public function __render(array $vars): string
165
    {
166
        // Get the template path
167
        $templatePath = $this->__path();
168
        if(!@is_readable($templatePath))
169
        {
170
            return '';
171
        }
172
173
        // Save the template properties.
174
        foreach($vars as $name => $value)
175
        {
176
            $this->__set((string)$name, $value);
177
        }
178
179
        // Render the template
180
        $renderer = function() use($templatePath) {
181
            ob_start();
182
            include $templatePath;
183
            $content = ob_get_clean();
184
185
            return $this->__extends__ === null ? $content :
186
                // Render the extended template with the same properties.
187
                $this->__extends__->__render($this->__properties__);
188
        };
189
190
        // Call the closure in the context of this object.
191
        // So the keyword '$this' in the template will refer to this object.
192
        return call_user_func($renderer->bindTo($this));
193
    }
194
}
195