Template::addStorage()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 6
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
use Biurad\UI\Storage\ChainStorage;
25
26
/**
27
 * The template render resolver.
28
 *
29
 * @author Divine Niiquaye Ibok <[email protected]>
30
 */
31
final class Template
32
{
33
    /** Namespace separator */
34
    public const NS_SEPARATOR = '::';
35
36
    /** @var array<string,mixed> */
37
    public $globals = [];
38
39
    /** @var StorageInterface */
40
    private $storage;
41
42
    /** @var string|null */
43
    private $cacheDir;
44
45
    /** @var array<int,RenderInterface> */
46
    private $renders = [];
47
48
    /** @var array<string,array<int,string>> */
49
    private $namespaces = [];
50
51
    /** @var array<string,array<int,mixed>> */
52
    private $loadedTemplates = [];
53
54 8
    public function __construct(StorageInterface $storage, string $cacheDir = null)
55
    {
56 8
        $this->storage = $storage;
57 8
        $this->cacheDir = $cacheDir;
58
    }
59
60
    /**
61
     * Add a namespace hint to the finder.
62
     *
63
     * @param string|string[] $hints list of directories to look into
64
     */
65 4
    public function addNamespace(string $namespace, $hints): void
66
    {
67 4
        if (!\is_array($hints)) {
68 4
            $hints = [$hints];
69
        }
70 4
        $this->namespaces[$namespace] = \array_merge($this->namespaces[$namespace] ?? [], $hints);
71
    }
72
73
    /**
74
     * Adds a new storage system to templating.
75
     *
76
     * This can be useful as e.g. cached templates may be fetched
77
     * from database and used in runtime.
78
     */
79
    public function addStorage(StorageInterface $storage): void
80
    {
81
        if ($this->storage instanceof ChainStorage) {
82
            $this->storage->addStorage($storage);
83
        } else {
84
            $this->storage = new ChainStorage([$this->storage, $storage]);
85
        }
86
    }
87
88
    /**
89
     * Get the storage system used.
90
     */
91
    public function getStorage(): StorageInterface
92
    {
93
        return $this->storage;
94
    }
95
96
    /**
97
     * Attach the view render(s).
98
     */
99 6
    public function addRender(RenderInterface ...$renders): void
100
    {
101 6
        foreach ($renders as $render) {
102 6
            if ($render instanceof CacheInterface) {
103 4
                $render->withCache($this->cacheDir);
104
            }
105 6
            $this->renders[] = $render->withLoader($this);
106
        }
107
    }
108
109
    /**
110
     * Get all associated view engines.
111
     *
112
     * @return array<int,RenderInterface>
113
     */
114 1
    public function getRenders(): array
115
    {
116 1
        return $this->renders;
117
    }
118
119
    /**
120
     * Get a template render by its supported file extension.
121
     */
122 4
    public function getRender(string $byFileExtension): RenderInterface
123
    {
124 4
        foreach ($this->renders as $renderLoader) {
125 3
            if (\in_array($byFileExtension, $renderLoader->getExtensions(), true)) {
126 3
                return $renderLoader;
127
            }
128
        }
129
130 1
        throw new LoaderException(\sprintf('Could not find a render for file extension "%s".', $byFileExtension));
131
    }
132
133
    /**
134
     * Renders a template.
135
     *
136
     * @param string              $template   A template name or a namespace name to path
137
     * @param array<string,mixed> $parameters An array of parameters to pass to the template
138
     *
139
     * @throws LoaderException if the template cannot be rendered
140
     *
141
     * @return string The evaluated template as a string
142
     */
143 7
    public function render(string $template, array $parameters = []): string
144
    {
145 7
        $loadedTemplate = $this->find($template, $renderLoader);
146
147 7
        if (!isset($loadedTemplate, $renderLoader)) {
148 1
            throw new LoaderException(\sprintf('Unable to load template for "%s", file does not exist.', $template));
149
        }
150
151 6
        return $renderLoader->render($loadedTemplate, \array_merge($this->globals, $parameters));
152
    }
153
154
    /**
155
     * Find the template file that exist, then render it contents.
156
     *
157
     * @param array<int,string> $templates
158
     * @param array<string,mixed> $parameters
159
     */
160 1
    public function renderTemplates(array $templates, array $parameters): ?string
161
    {
162 1
        $renderLoader = null;
163
164 1
        foreach ($templates as $template) {
165 1
            $loadedTemplate = $this->find($template, $renderLoader);
166
167 1
            if (isset($loadedTemplate, $renderLoader)) {
168 1
                return $renderLoader->render($loadedTemplate, \array_merge($this->globals, $parameters));
169
            }
170
        }
171
172 1
        return null;
173
    }
174
175
    /**
176
     * Get source for given template. Path might include namespace prefix or extension.
177
     *
178
     * @param string $template A template name or a namespace name to path
179
     *
180
     * @throws LoaderException if unable to load template from namespace
181
     *
182
     * @return string|null Expects the template absolute file or null
183
     */
184 7
    public function find(string $template, RenderInterface &$render = null): ?string
185
    {
186 7
        if ($cachedTemplate = &$this->loadedTemplates[$template] ?? null) {
187 3
            [$loadedTemplate, $renderOffset] = $cachedTemplate;
188
189 3
            if (2 === \func_num_args()) {
190 3
                $render = $this->renders[$renderOffset];
191
            }
192
193 3
            return $loadedTemplate;
194
        }
195
196 7
        if (\str_contains($template, self::NS_SEPARATOR)) {
197 5
            [$namespace, $template] = \explode(self::NS_SEPARATOR, \ltrim($template, '@#'), 2);
198
199 5
            if (empty($namespaces = $this->namespaces[$namespace] ?? [])) {
200 1
                throw new LoaderException(\sprintf('No hint source(s) defined for [%s] namespace.', $namespace));
201
            }
202
        }
203
204 7
        if (\str_starts_with($template, 'html:')) {
205 1
            [$requestHtml, $template] = ['html:', \substr($template, 5)];
206
        }
207
208 7
        $templateExt = \pathinfo($template, \PATHINFO_EXTENSION);
209
210 7
        foreach ($this->renders as $offset => $renderLoader) {
211 6
            $loadedTemplate = null;
212
213 6
            if (\in_array($templateExt, $extensions = $renderLoader->getExtensions(), true)) {
214 3
                $loadedTemplate = $this->storage->load($template, $namespaces ?? []);
215
216 3
                if (null !== $loadedTemplate) {
217 3
                    break;
218
                }
219
            }
220
221 6
            foreach ($extensions as $extension) {
222 6
                $loadedTemplate = $this->storage->load(\str_replace(['\\', '.'], '/', $template) . '.' . $extension, $namespaces ?? []);
223
224 6
                if (null !== $loadedTemplate) {
225 6
                    break 2;
226
                }
227
            }
228
        }
229
230 7
        if (isset($loadedTemplate, $renderLoader, $offset)) {
231 6
            $cachedTemplate = [$loadedTemplate = ($requestHtml ?? null) . $loadedTemplate, $offset];
232
233 6
            if (2 === \func_num_args()) {
234 6
                $render = $renderLoader;
235
            }
236
        }
237
238 7
        return $loadedTemplate ?? null;
239
    }
240
}
241