Passed
Push — master ( fbdae9...66097f )
by Divine Niiquaye
10:37
created

Template::getRender()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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