Passed
Push — master ( a9f58f...13a36e )
by Divine Niiquaye
38:13 queued 25:24
created

PhpNativeRender::escape()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Biurad opensource projects.
7
 *
8
 * PHP version 7.2 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Biurad\UI\Renders;
19
20
use Biurad\UI\Exceptions\RenderException;
21
use Biurad\UI\Helper\EscaperHelper;
22
use Biurad\UI\Helper\SlotsHelper;
23
use Biurad\UI\Interfaces\HelperInterface;
24
use Biurad\UI\Template;
25
26
/**
27
 * A PHP native template render based.
28
 *
29
 * @author Divine Niiquaye Ibok <[email protected]>
30
 */
31
final class PhpNativeRender extends AbstractRender implements \ArrayAccess
32
{
33
    protected const EXTENSIONS = ['phtml', 'html', 'php'];
34
35
    /** @var string */
36
    protected $current;
37
38
    /** @var HelperInterface[] */
39
    protected $helpers = [];
40
41
    /** @var array<string,callable> */
42
    protected $parents = [];
43
44
    /** @var array<int,mixed> */
45
    protected $stack = [];
46
47
    /** @var string */
48
    protected $charset = 'UTF-8';
49
50
    /** @var string|null */
51
    private $evalTemplate;
52
53
    /** @var array<string,mixed>|null */
54
    private $evalParameters;
55
56
    /**
57
     * PhpNativeEngine constructor.
58
     *
59
     * @param string[]          $extensions
60
     * @param HelperInterface[] $helpers    An array of helper instances
61
     */
62 6
    public function __construct(array $extensions = self::EXTENSIONS, array $helpers = [])
63
    {
64 6
        $this->extensions = $extensions;
65 6
        $this->addHelpers(\array_merge([new SlotsHelper(), new EscaperHelper()], $helpers));
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 6
    public function render(string $template, array $parameters): string
72
    {
73 6
        $this->current = $key = \hash('sha256', $template);
74
75 6
        if (false === $content = $this->evaluate($template, $parameters)) {
76
            throw new \RuntimeException(\sprintf('The template "%s" cannot be rendered.', $template));
77
        }
78
79 6
        if (isset($this->parents[$key])) {
80
            /** @var SlotsHelper */
81 5
            $slots = $this->get('slots');
82
83 5
            $this->stack[] = $slots->get('_content');
0 ignored issues
show
Bug introduced by
The method get() does not exist on Biurad\UI\Interfaces\HelperInterface. It seems like you code against a sub-type of Biurad\UI\Interfaces\HelperInterface such as Biurad\UI\Helper\SlotsHelper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

83
            /** @scrutinizer ignore-call */ 
84
            $this->stack[] = $slots->get('_content');
Loading history...
84 5
            $slots->set('_content', $content);
0 ignored issues
show
Bug introduced by
The method set() does not exist on Biurad\UI\Interfaces\HelperInterface. It seems like you code against a sub-type of Biurad\UI\Interfaces\HelperInterface such as Biurad\UI\Helper\SlotsHelper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

84
            $slots->/** @scrutinizer ignore-call */ 
85
                    set('_content', $content);
Loading history...
85
86 5
            $content = $this->parents[$key]($parameters);
87
88 4
            $slots->set('_content', (string) \array_pop($this->stack));
89
        }
90
91 6
        return $content;
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     *
97
     * @param string $offset The helper name
98
     *
99
     * @throws \InvalidArgumentException if the helper is not defined
100
     *
101
     * @return HelperInterface The helper value
102
     */
103 4
    #[\ReturnTypeWillChange]
104
    public function offsetGet($offset)
105
    {
106 4
        return $this->get($offset);
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     *
112
     * @param string $offset The helper name
113
     */
114
    public function offsetExists($offset): bool
115
    {
116
        return isset($this->helpers[$offset]);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     *
122
     * @param HelperInterface $offset The helper name
123
     * @param string|null     $value
124
     */
125
    public function offsetSet($offset, $value): void
126
    {
127
        $this->set($offset, $value);
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     *
133
     * @param string $offset The helper name
134
     */
135
    public function offsetUnset($offset): void
136
    {
137
        throw new \LogicException(\sprintf('You can\'t unset a helper (%s).', $offset));
138
    }
139
140
    /**
141
     * Adds some helpers.
142
     *
143
     * @param HelperInterface[] $helpers An array of helper
144
     */
145 6
    public function addHelpers(array $helpers): void
146
    {
147 6
        foreach ($helpers as $alias => $helper) {
148 6
            $this->set($helper, \is_int($alias) ? null : $alias);
149
        }
150
    }
151
152
    /**
153
     * Sets the helpers.
154
     *
155
     * @param array<int,HelperInterface> $helpers An array of helper
156
     */
157
    public function setHelpers(array $helpers): void
158
    {
159
        $this->helpers = [];
160
        $this->addHelpers($helpers);
161
    }
162
163
    /**
164
     * Sets a new helper resolve.
165
     */
166 6
    public function set(HelperInterface $helper, string $alias = null): void
167
    {
168 6
        $this->helpers[$helper->getName()] = $helper;
169
170 6
        if (null !== $alias) {
171
            $this->helpers[$alias] = $helper;
172
        }
173
174 6
        $helper->setCharset($this->charset);
175
    }
176
177
    /**
178
     * Returns true if the helper if defined.
179
     *
180
     * @return bool true if the helper is defined, false otherwise
181
     */
182
    public function has(string $name)
183
    {
184
        return isset($this->helpers[$name]);
185
    }
186
187
    /**
188
     * Gets a helper value.
189
     *
190
     * @throws \InvalidArgumentException if the helper is not defined
191
     *
192
     * @return HelperInterface The helper instance
193
     */
194 5
    public function get(string $name)
195
    {
196 5
        if (!isset($this->helpers[$name])) {
197
            throw new \InvalidArgumentException(\sprintf('The helper "%s" is not defined.', $name));
198
        }
199
200 5
        return $this->helpers[$name];
201
    }
202
203
    /**
204
     * Decorates the current template with another one.
205
     *
206
     * @param string $template The decorator logical name
207
     */
208 5
    public function extend(string $template): void
209
    {
210 5
        $this->parents[$this->current] = function (array $parameters) use ($template): string {
211 5
            $templateRender = $this->loader;
212
213 5
            if (!$templateRender instanceof Template) {
214
                throw new RenderException(\sprintf('Extending template with hash "%s" to "%s" failed. Required %s instance.', $this->current, $template, Template::class));
215
            }
216
217 5
            return $templateRender->render($template, $parameters);
218 5
        };
219
    }
220
221
    /**
222
     * Escapes a string by using the current charset.
223
     *
224
     * @param mixed $value A variable to escape
225
     *
226
     * @return mixed The escaped value
227
     */
228 4
    public function escape($value, string $context = 'html')
229
    {
230 4
        return $this->get(__FUNCTION__)->{$context}($value);
231
    }
232
233
    /**
234
     * Sets the charset to use.
235
     */
236
    public function setCharset(string $charset): void
237
    {
238
        if ('UTF8' === $charset = \strtoupper($charset)) {
239
            $charset = 'UTF-8'; // iconv on Windows requires "UTF-8" instead of "UTF8"
240
        }
241
        $this->charset = $charset;
242
243
        foreach ($this->helpers as $helper) {
244
            $helper->setCharset($this->charset);
245
        }
246
    }
247
248
    /**
249
     * Gets the current charset.
250
     *
251
     * @return string The current charset
252
     */
253
    public function getCharset(): string
254
    {
255
        return $this->charset;
256
    }
257
258
    /**
259
     * Evaluates a template.
260
     *
261
     * @param array<string,mixed> $parameters
262
     *
263
     * @throws \InvalidArgumentException
264
     *
265
     * @return false|string The evaluated template,or false if the engine is unable to render the template
266
     */
267 6
    protected function evaluate(string $template, array $parameters = [])
268
    {
269 6
        $this->evalTemplate = self::loadHtml($template) ?? $template;
270 6
        $this->evalParameters = $parameters;
271
272 6
        unset($template, $parameters);
273
274 6
        if (isset($this->evalParameters['this'])) {
275
            throw new \InvalidArgumentException('Invalid parameter (this).');
276
        }
277
278
        // the template variable is exposed to the require file below
279 6
        $template = $this->loader;
280
281 6
        \extract($this->evalParameters, \EXTR_SKIP);
282 6
        $this->evalParameters = null;
283
284 6
        \ob_start();
285
286 6
        if (!\file_exists($this->evalTemplate)) {
287 2
            eval('; ?>' . $this->evalTemplate . '<?php ;');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
288 2
            $this->evalTemplate = null;
289
290 2
            return \ob_get_clean();
291
        }
292
293 4
        require $this->evalTemplate;
294 4
        $this->evalTemplate = null;
295
296 4
        return \ob_get_clean();
297
    }
298
}
299