Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
80 / 80
100.00% covered (success)
100.00%
20 / 20
CRAP
100.00% covered (success)
100.00%
1 / 1
Stream
100.00% covered (success)
100.00%
80 / 80
100.00% covered (success)
100.00%
20 / 20
41
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 setTemplate
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setMaster
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getBlocks
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBlock
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMasterBlocks
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMasterBlock
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setBlocks
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setBlock
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setMasterBlocks
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setMasterBlock
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getParent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMaster
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isFile
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 parseParent
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 parseIncludes
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 parseBlocks
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
8
 renderTemplate
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\View\Template;
15
16/**
17 * View stream template class
18 *
19 * @category   Pop
20 * @package    Pop\View
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    4.0.0
25 */
26class Stream extends AbstractTemplate
27{
28
29    /**
30     * View template file
31     * @var ?string
32     */
33    protected ?string $file = null;
34
35    /**
36     * View parent template
37     * @var ?Stream
38     */
39    protected ?Stream $parent = null;
40
41    /**
42     * Block templates
43     * @var array
44     */
45    protected array $blocks = [];
46
47    /**
48     * Master template
49     * @var ?string
50     */
51    protected ?string $master = null;
52
53    /**
54     * Master block templates
55     * @var array
56     */
57    protected array $masterBlocks = [];
58
59    /**
60     * Constructor
61     *
62     * Instantiate the view stream template object
63     *
64     * @param string $template
65     */
66    public function __construct(string $template)
67    {
68        $this->setTemplate($template);
69
70        // Parse parent template
71        $this->parseParent();
72
73        // Parse includes
74        $this->parseIncludes();
75
76        // Parse blocks
77        $this->parseBlocks();
78    }
79
80    /**
81     * Set view template with auto-detect
82     *
83     * @param  string $template
84     * @return static
85     */
86    public function setTemplate(string $template): static
87    {
88        if ((strlen($template) <= 255) && file_exists($template)) {
89            $this->template = file_get_contents($template);
90            $this->file     = $template;
91        } else {
92            $this->template = $template;
93        }
94        return $this;
95    }
96
97    /**
98     * Set master
99     *
100     * @param  string $master
101     * @return static
102     */
103    public function setMaster(string $master): static
104    {
105        $this->master = $master;
106        return $this;
107    }
108
109    /**
110     * Get blocks
111     *
112     * @return array
113     */
114    public function getBlocks(): array
115    {
116        return $this->blocks;
117    }
118
119    /**
120     * Get block by name
121     *
122     * @param  string $name
123     * @return string|null
124     */
125    public function getBlock(string $name): string|null
126    {
127        return $this->blocks[$name] ?? null;
128    }
129
130    /**
131     * Get master blocks
132     *
133     * @return array
134     */
135    public function getMasterBlocks(): array
136    {
137        return $this->masterBlocks;
138    }
139
140    /**
141     * Get master block by name
142     *
143     * @param  string $name
144     * @return string|null
145     */
146    public function getMasterBlock(string $name): string|null
147    {
148        return $this->masterBlocks[$name] ?? null;
149    }
150
151    /**
152     * Set blocks
153     *
154     * @param  array $blocks
155     * @return static
156     */
157    public function setBlocks(array $blocks): static
158    {
159        $this->blocks = $blocks;
160        return $this;
161    }
162
163    /**
164     * Set block
165     *
166     * @param  string $name
167     * @param  string $value
168     * @return static
169     */
170    public function setBlock($name, $value): static
171    {
172        $this->blocks[$name] = $value;
173        return $this;
174    }
175
176    /**
177     * Set master blocks
178     *
179     * @param  array $blocks
180     * @return static
181     */
182    public function setMasterBlocks(array $blocks): static
183    {
184        $this->masterBlocks = $blocks;
185        return $this;
186    }
187
188    /**
189     * Set master block
190     *
191     * @param  string $name
192     * @param  string $value
193     * @return static
194     */
195    public function setMasterBlock(string $name, string $value): static
196    {
197        $this->masterBlocks[$name] = $value;
198        return $this;
199    }
200
201    /**
202     * Get parent
203     *
204     * @return static|null
205     */
206    public function getParent(): static|null
207    {
208        return $this->parent;
209    }
210
211    /**
212     * Get master
213     *
214     * @return string
215     */
216    public function getMaster(): string
217    {
218        return $this->master;
219    }
220
221    /**
222     * Determine if the template stream is from a file
223     *
224     * @return bool
225     */
226    public function isFile(): bool
227    {
228        return ($this->file !== null);
229    }
230
231    /**
232     * Determine if the template stream is from a string
233     *
234     * @return bool
235     */
236    public function isString(): bool
237    {
238        return ($this->file === null);
239    }
240
241    /**
242     * Render the view and return the output
243     *
244     * @param  ?array $data
245     * @return string
246     */
247    public function render(?array $data = null): string
248    {
249        if ($data !== null) {
250            $this->data = $data;
251        }
252        $this->renderTemplate();
253        return $this->output;
254    }
255
256    /**
257     * Parse template parent/child blocks
258     *
259     * @return void
260     */
261    protected function parseParent(): void
262    {
263        $matches = [];
264        preg_match_all('/\{\{\@extends(.*?)\}\}/s', $this->template, $matches);
265
266        if (isset($matches[0]) && isset($matches[0][0])) {
267            foreach ($matches[0] as $key => $match) {
268                $tmpl = trim($matches[1][$key]);
269                if ($tmpl != $this->file) {
270                    $dir            = ($this->isFile()) ? dirname($this->file) . DIRECTORY_SEPARATOR : null;
271                    $this->template = str_replace($match, '', $this->template);
272                    $this->parent   = new Stream($dir . $tmpl);
273                }
274            }
275        }
276    }
277
278    /**
279     * Parse template includes
280     *
281     * @return void
282     */
283    protected function parseIncludes(): void
284    {
285        $matches = [];
286        preg_match_all('/\{\{\@include(.*?)\}\}/s', $this->template, $matches);
287
288        if (isset($matches[0]) && isset($matches[0][0])) {
289            foreach ($matches[0] as $key => $match) {
290                $tmpl = trim($matches[1][$key]);
291                if ($tmpl != $this->file) {
292                    $dir  = ($this->isFile()) ? dirname($this->file) . DIRECTORY_SEPARATOR : null;
293                    $view = new Stream($dir . $tmpl);
294                    $this->template = str_replace($match, $view->render($this->data), $this->template);
295                }
296            }
297        }
298    }
299
300    /**
301     * Parse template parent/child blocks
302     *
303     * @return void
304     */
305    protected function parseBlocks(): void
306    {
307        $matches = [];
308        preg_match_all('/\{\{(.*?)\{\{\/(.*?)\}\}/s', $this->template, $matches);
309
310        if (isset($matches[0]) && isset($matches[0][0])) {
311            foreach ($matches[0] as $match) {
312                $name    = substr($match, 2);
313                $name    = substr($name, 0, strpos($name, '}}'));
314                $content = substr($match, (strpos($match, '}}') + 2));
315                $content = substr($content, 0, strpos($content, '{{/'));
316                $this->blocks[$name] = $content;
317            }
318        }
319
320        $parent = $this->parent;
321
322        if ($parent === null) {
323            $this->setMaster($this->template);
324            $this->setMasterBlocks($this->blocks);
325        }
326
327        while ($parent !== null) {
328            $this->setMaster($parent->getMaster());
329            $this->setMasterBlocks($parent->getMasterBlocks());
330
331            foreach ($this->blocks as $block => $tmpl) {
332                $this->setBlock('header', str_replace('{{parent}}', $parent->getBlock($block), $tmpl));
333            }
334
335            $parent = $parent->getParent();
336        }
337
338        $this->template = $this->master;
339        foreach ($this->blocks as $block => $tmpl) {
340            $this->template = str_replace(
341                '{{' . $block . '}}' . $this->getMasterBlock($block) . '{{/' . $block . '}}',
342                $tmpl,
343                $this->template
344            );
345        }
346    }
347
348    /**
349     * Render view template string
350     *
351     * @return void
352     */
353    protected function renderTemplate(): void
354    {
355        if ($this->data !== null) {
356            $this->output = $this->template;
357
358            // Parse array values
359            $this->output = Stream\Parser::parseArrays($this->template, $this->data, $this->output);
360
361            // Parse conditionals
362            $this->output = Stream\Parser::parseConditionals($this->template, $this->data, $this->output);
363
364            // Parse scalar values
365            $this->output = Stream\Parser::parseScalars($this->data, $this->output);
366        }
367    }
368
369}