Passed
Push — master ( dd2095...cc3241 )
by Eric
02:09
created

Template::readFile()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Esi\SimpleTpl.
7
 *
8
 * (c) 2006 - 2025 Eric Sizemore <[email protected]>
9
 *
10
 * This file is licensed under The MIT License. For the full copyright and
11
 * license information, please view the LICENSE.md file that was distributed
12
 * with this source code.
13
 */
14
15
namespace Esi\SimpleTpl;
16
17
use InvalidArgumentException;
18
use LogicException;
19
use Psr\Cache\CacheItemPoolInterface;
20
use Psr\Cache\InvalidArgumentException as PsrInvalidArgumentException;
21
use RuntimeException;
22
23
use function array_keys;
24
use function array_map;
25
use function array_merge;
26
use function array_values;
27
use function crc32;
28
use function dechex;
29
use function file_get_contents;
30
use function str_replace;
31
32
final class Template
33
{
34
    private string $leftDelimiter = '{';
35
36
    private string $rightDelimiter = '}';
37
38
    /**
39
     * @var array<string>
40
     */
41
    private array $tplVars = [];
42
43
    /**
44
     * Constructor.
45
     */
46 15
    public function __construct(private readonly ?CacheItemPoolInterface $cacheItemPool = null) {}
47
48
    /**
49
     * Clears the cache.
50
     *
51
     * @return bool True if the cache was cleared successfully, false otherwise.
52
     */
53 2
    public function clearCache(): bool
54
    {
55 2
        if (!$this->isUsingCache()) {
56 1
            return true;
57
        }
58
59 1
        return $this->cacheItemPool->clear();
60
    }
61
62
    /**
63
     * Displays the parsed template.
64
     *
65
     * @param string $tplFile The path to the template file.
66
     *
67
     * @throws PsrInvalidArgumentException
68
     */
69 1
    public function display(string $tplFile): void
70
    {
71 1
        echo $this->parse($tplFile);
72
    }
73
74
    /**
75
     * Gets the left delimiter.
76
     *
77
     * @return string The left delimiter.
78
     */
79 1
    public function getLeftDelimiter(): string
80
    {
81 1
        return $this->leftDelimiter;
82
    }
83
84
    /**
85
     * Gets the right delimiter.
86
     *
87
     * @return string The right delimiter.
88
     */
89 1
    public function getRightDelimiter(): string
90
    {
91 1
        return $this->rightDelimiter;
92
    }
93
94
    /**
95
     * Gets the template variables.
96
     *
97
     * @return array<string> The template variables.
98
     */
99 2
    public function getTplVars(): array
100
    {
101 2
        return $this->tplVars;
102
    }
103
104
    /**
105
     * Checks if caching is enabled.
106
     *
107
     * @psalm-assert-if-true !null $this->cacheItemPool
108
     *
109
     * @return bool True if caching is enabled, false otherwise.
110
     */
111 8
    public function isUsingCache(): bool
112
    {
113 8
        return $this->cacheItemPool instanceof CacheItemPoolInterface;
114
    }
115
116
    /**
117
     * Parses the template file and replaces variables.
118
     *
119
     * @param string $tplFile The path to the template file.
120
     *
121
     * @throws InvalidArgumentException    If the file cannot be found or read.
122
     * @throws RuntimeException            If the file has no content.
123
     * @throws LogicException              If there are no template variables set.
124
     * @throws PsrInvalidArgumentException
125
     *
126
     * @return string The parsed template content.
127
     */
128 11
    public function parse(string $tplFile): string
129
    {
130 11
        $this->validateFile($tplFile);
131
132 8
        $cacheKey = $this->generateCacheKey($tplFile);
133
134 8
        if ($this->isUsingCache() && $this->cacheItemPool->hasItem($cacheKey)) {
135
            /**
136
             * @var string $templateCache
137
             */
138 1
            $templateCache = $this->cacheItemPool->getItem($cacheKey)->get();
139
140 1
            return $templateCache;
141
        }
142
143 7
        if ($this->tplVars === []) {
144 1
            throw new LogicException('Unable to parse template, no tplVars found');
145
        }
146
147 6
        $contents = $this->readFile($tplFile);
148
149
        // Perform replacements
150 5
        $contents = $this->doReplacements($contents);
151
152 5
        if ($this->isUsingCache()) {
153 3
            $this->cacheItemPool->save($this->cacheItemPool->getItem($cacheKey)->set($contents));
154
        }
155
156 5
        return $contents;
157
    }
158
159
    /**
160
     * Refreshes the cache for a specific template file.
161
     *
162
     * @param string $tplFile The path to the template file.
163
     *
164
     * @throws PsrInvalidArgumentException
165
     *
166
     * @return bool True if the cache was refreshed successfully, false otherwise.
167
     */
168 2
    public function refreshCache(string $tplFile): bool
169
    {
170 2
        if (!$this->isUsingCache()) {
171 1
            return true;
172
        }
173
174 1
        return $this->cacheItemPool->deleteItem(self::generateCacheKey($tplFile));
0 ignored issues
show
Bug Best Practice introduced by
The method Esi\SimpleTpl\Template::generateCacheKey() is not static, but was called statically. ( Ignorable by Annotation )

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

174
        return $this->cacheItemPool->deleteItem(self::/** @scrutinizer ignore-call */ generateCacheKey($tplFile));
Loading history...
175
    }
176
177
    /**
178
     * Sets the left delimiter.
179
     *
180
     * @param string $delimiter The left delimiter.
181
     */
182 1
    public function setLeftDelimiter(string $delimiter): void
183
    {
184 1
        $this->leftDelimiter = $delimiter;
185
    }
186
187
    /**
188
     * Sets the right delimiter.
189
     *
190
     * @param string $delimiter The right delimiter.
191
     */
192 1
    public function setRightDelimiter(string $delimiter): void
193
    {
194 1
        $this->rightDelimiter = $delimiter;
195
    }
196
197
    /**
198
     * Sets the template variables.
199
     *
200
     * An empty array can be passed to clear/reset previously assigned variables.
201
     *
202
     * @param array<string> $tplVars Template variables and replacements.
203
     */
204 10
    public function setTplVars(array $tplVars): void
205
    {
206 10
        if ($tplVars === []) {
207 2
            $this->tplVars = [];
208
209 2
            return;
210
        }
211
212 9
        $this->tplVars = array_merge($this->tplVars, $tplVars);
213
    }
214
215
    /**
216
     * Replaces template variables in the content.
217
     *
218
     * @param string $contents The content of the template file.
219
     *
220
     * @return string The content with template variables replaced.
221
     */
222 5
    private function doReplacements(string $contents): string
223
    {
224 5
        return str_replace(
225 5
            array_map(
226 5
                fn (int|string $find): string => \sprintf('%s%s%s', $this->leftDelimiter, $find, $this->rightDelimiter),
227 5
                array_keys($this->tplVars)
228 5
            ),
229 5
            array_values($this->tplVars),
230 5
            $contents
231 5
        );
232
    }
233
234
    /**
235
     * Generates a cache key for a template file.
236
     *
237
     * @param string $file The path to the template file.
238
     *
239
     * @return string The generated cache key.
240
     */
241 8
    private function generateCacheKey(string $file): string
242
    {
243 8
        return \sprintf('template_%s', dechex(crc32($file)));
244
    }
245
246
    /**
247
     * Reads the content of the template file.
248
     *
249
     * @param string $tplFile The path to the template file.
250
     *
251
     * @throws RuntimeException If the file has no valid content.
252
     *
253
     * @return string The content of the template file.
254
     */
255 6
    private function readFile(string $tplFile): string
256
    {
257 6
        $contents = file_get_contents($tplFile);
258
259 6
        if ($contents === '' || $contents === false) {
260 1
            throw new RuntimeException(\sprintf('"%s" does not appear to have any valid content.', $tplFile));
261
        }
262
263 5
        return $contents;
264
    }
265
266
    /**
267
     * Validates the template file.
268
     *
269
     * @param string $tplFile The path to the template file.
270
     *
271
     * @throws InvalidArgumentException If the file does not exist or is not readable.
272
     */
273 11
    private function validateFile(string $tplFile): void
274
    {
275 11
        if (!is_file($tplFile) || !is_readable($tplFile)) {
276 3
            throw new InvalidArgumentException(\sprintf('"%s" does not exist or is not a readable file.', $tplFile));
277
        }
278
    }
279
}
280