Passed
Push — master ( 417605...635760 )
by Divine Niiquaye
11:55
created

Template::render()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 3
b 0
f 0
nc 2
nop 2
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
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;
19
20
use Biurad\UI\Exceptions\LoaderException;
21
use Biurad\UI\Interfaces\CacheInterface;
22
use Biurad\UI\Interfaces\RenderInterface;
23
use Biurad\UI\Interfaces\StorageInterface;
24
25
/**
26
 * The template render resolver.
27
 *
28
 * @author Divine Niiquaye Ibok <[email protected]>
29
 */
30
final class Template
31
{
32
    /** Namespace separator */
33
    public const NS_SEPARATOR = '::';
34
35
    /** @var array<string,mixed> */
36
    public $globals = [];
37
38
    /** @var StorageInterface */
39
    private $storage;
40
41
    /** @var string|null */
42
    private $cacheDir;
43
44
    /** @var array<int,RenderInterface> */
45
    private $renders = [];
46
47
    /** @var array<string,array<int,string>> */
48
    private $namespaces = [];
49
50
    /** @var array<string,array<int,mixed>> */
51
    private $loadedTemplates = [];
52
53 8
    public function __construct(StorageInterface $storage, string $cacheDir = null)
54
    {
55 8
        $this->storage = $storage;
56 8
        $this->cacheDir = $cacheDir;
57
    }
58
59
    /**
60
     * Add a namespace hint to the finder.
61
     *
62
     * @param string|string[] $hints list of directories to look into
63
     */
64 4
    public function addNamespace(string $namespace, $hints): void
65
    {
66 4
        if (!\is_array($hints)) {
67 4
            $hints = [$hints];
68
        }
69 4
        $this->namespaces[$namespace] = \array_merge($this->namespaces[$namespace] ?? [], $hints);
70
    }
71
72
    /**
73
     * Attach the view render(s).
74
     */
75 6
    public function addRender(RenderInterface ...$renders): void
76
    {
77 6
        foreach ($renders as $render) {
78 6
            if ($render instanceof CacheInterface) {
79 4
                $render->withCache($this->cacheDir);
80
            }
81 6
            $this->renders[] = $render->withLoader($this);
82
        }
83
    }
84
85
    /**
86
     * Get all associated view engines.
87
     *
88
     * @return array<int,RenderInterface>
89
     */
90 1
    public function getRenders(): array
91
    {
92 1
        return $this->renders;
93
    }
94
95
    /**
96
     * Get a template render by its supported file extension.
97
     */
98 4
    public function getRender(string $byFileExtension): RenderInterface
99
    {
100 4
        foreach ($this->renders as $renderLoader) {
101 3
            if (\in_array($byFileExtension, $renderLoader->getExtensions(), true)) {
102 3
                return $renderLoader;
103
            }
104
        }
105
106 1
        throw new LoaderException(\sprintf('Could not find a render for file extension "%s".', $byFileExtension));
107
    }
108
109
    /**
110
     * Renders a template.
111
     *
112
     * @param string              $template   A template name or a namespace name to path
113
     * @param array<string,mixed> $parameters An array of parameters to pass to the template
114
     *
115
     * @throws LoaderException if the template cannot be rendered
116
     *
117
     * @return string The evaluated template as a string
118
     */
119 7
    public function render(string $template, array $parameters = []): string
120
    {
121 7
        $loadedTemplate = $this->find($template, $renderLoader);
122
123 7
        if (null === $loadedTemplate) {
124 2
            throw new LoaderException(\sprintf('Unable to load template for "%s", file does not exist.', $template));
125
        }
126
127 6
        return $renderLoader->render($loadedTemplate, \array_merge($this->globals, $parameters));
0 ignored issues
show
Bug introduced by
The method render() does not exist on null. ( Ignorable by Annotation )

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

127
        return $renderLoader->/** @scrutinizer ignore-call */ render($loadedTemplate, \array_merge($this->globals, $parameters));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
128
    }
129
130
    /**
131
     * Find the template file that exist, then render it contents.
132
     *
133
     * @param array<int,string> $templates
134
     * @param array<string,mixed> $parameters
135
     */
136 1
    public function renderTemplates(array $templates, array $parameters): ?string
137
    {
138 1
        foreach ($templates as $template) {
139
            try {
140 1
                return $this->render($template, $parameters);
141 1
            } catch (LoaderException $e) {
142 1
                continue;
143
            }
144
        }
145
146 1
        return null;
147
    }
148
149
    /**
150
     * Get source for given template. Path might include namespace prefix or extension.
151
     *
152
     * @param string $template A template name or a namespace name to path
153
     *
154
     * @throws LoaderException if unable to load template from namespace
155
     *
156
     * @return string|null Expects the template absolute file or null
157
     */
158 7
    public function find(string $template, RenderInterface &$render = null): ?string
159
    {
160 7
        if ($cachedTemplate = &$this->loadedTemplates[$template] ?? null) {
161 3
            [$loadedTemplate, $renderOffset] = $cachedTemplate;
162
163 3
            if (2 === \func_num_args()) {
164 3
                $render = $this->renders[$renderOffset];
165
            }
166
167 3
            return $loadedTemplate;
168
        }
169
170 7
        if (\str_contains($template, self::NS_SEPARATOR)) {
171 5
            [$namespace, $template] = \explode(self::NS_SEPARATOR, \ltrim($template, '@#'), 2);
172
173 5
            if (empty($namespaces = $this->namespaces[$namespace] ?? [])) {
174 1
                throw new LoaderException(\sprintf('No hint source(s) defined for [%s] namespace.', $namespace));
175
            }
176
        }
177
178 7
        if (\str_starts_with($template, 'html:')) {
179 1
            [$requestHtml, $template] = ['html:', \substr($template, 5)];
180
        }
181
182 7
        $templateExt = \pathinfo($template, \PATHINFO_EXTENSION);
183
184 7
        foreach ($this->renders as $offset => $renderLoader) {
185 6
            $loadedTemplate = null;
186
187 6
            if (\in_array($templateExt, $extensions = $renderLoader->getExtensions(), true)) {
188 3
                $loadedTemplate = $this->storage->load($template, $namespaces ?? []);
189
190 3
                if (null !== $loadedTemplate) {
191 3
                    break;
192
                }
193
            }
194
195 6
            foreach ($extensions as $extension) {
196 6
                $loadedTemplate = $this->storage->load(\str_replace(['\\', '.'], '/', $template) . '.' . $extension, $namespaces ?? []);
197
198 6
                if (null !== $loadedTemplate) {
199 6
                    break 2;
200
                }
201
            }
202
        }
203
204 7
        if (isset($loadedTemplate)) {
205 6
            $cachedTemplate = [$loadedTemplate = ($requestHtml ?? null) . $loadedTemplate, $offset];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $offset seems to be defined by a foreach iteration on line 184. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
206
207 6
            if (2 === \func_num_args()) {
208 6
                $render = $renderLoader;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $renderLoader seems to be defined by a foreach iteration on line 184. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
209
            }
210
        }
211
212 7
        return $loadedTemplate ?? null;
213
    }
214
}
215