Passed
Pull Request — master (#3)
by Chauncey
04:46 queued 01:58
created

AbstractLoader::findTemplateFile()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 8.8337
c 0
b 0
f 0
cc 6
nc 5
nop 1
1
<?php
2
3
namespace Charcoal\View;
4
5
use InvalidArgumentException;
6
7
// From PSR-3
8
use Psr\Log\LoggerAwareInterface;
9
use Psr\Log\LoggerAwareTrait;
10
11
// From 'charcoal-view'
12
use Charcoal\View\LoaderInterface;
13
14
/**
15
 * Base Template Loader
16
 *
17
 * Finds a template file in a collection of directory paths.
18
 */
19
abstract class AbstractLoader implements
20
    LoggerAwareInterface,
21
    LoaderInterface
22
{
23
    use LoggerAwareTrait;
24
25
    /**
26
     * @var string
27
     */
28
    private $basePath = '';
29
30
    /**
31
     * @var string[]
32
     */
33
    private $paths = [];
34
35
    /**
36
     * @var array
37
     */
38
    private $dynamicTemplates = [];
39
40
    /**
41
     * The cache of searched template files.
42
     *
43
     * @var array
44
     */
45
    private $fileCache = [];
46
47
    /**
48
     * Default constructor, if none is provided by the concrete class implementations.
49
     *
50
     * ## Required dependencies
51
     * - `logger` A PSR-3 logger
52
     *
53
     * @param array $data The class dependencies map.
54
     */
55
    public function __construct(array $data = null)
56
    {
57
        $this->setLogger($data['logger']);
58
        $this->setBasePath($data['base_path']);
59
        $this->setPaths($data['paths']);
60
    }
61
62
    /**
63
     * Load a template content
64
     *
65
     * @deprecated $GLOBALS['widget_template']
66
     *
67
     * @param  string $ident The template ident to load and render.
68
     * @throws InvalidArgumentException If the dynamic template identifier is not a string.
69
     * @return string
70
     */
71
    public function load($ident)
72
    {
73
        // Handle dynamic template
74
        if (substr($ident, 0, 1) === '$') {
75
            $tryLegacy = ($ident === '$widget_template');
76
77
            $ident = $this->dynamicTemplate(substr($ident, 1));
78
79
            // Legacy dynamic template hack
80
            if ($tryLegacy) {
81
                $ident = empty($GLOBALS['widget_template']) ? $ident : $GLOBALS['widget_template'];
82
                $this->logger->warning(sprintf(
83
                    '%s is deprecated in favor of %s: %s',
84
                    '$GLOBALS[\'widget_template\']',
85
                    'setDynamicTemplate()',
86
                    $ident
87
                ));
88
                if (!is_string($ident)) {
89
                    throw new InvalidArgumentException(
90
                        'Dynamic template ident (from "$widget_template") must be a string'
91
                    );
92
                }
93
            }
94
        }
95
96
        $file = $this->findTemplateFile($ident);
97
        if ($file === null || $file === '') {
98
            return $ident;
99
        }
100
101
        return file_get_contents($file);
102
    }
103
104
    /**
105
     * @param  string $varName The name of the variable to get template ident from.
106
     * @throws InvalidArgumentException If the var name is not a string.
107
     * @return string
108
     */
109
    public function dynamicTemplate($varName)
110
    {
111
        if (!is_string($varName)) {
112
            throw new InvalidArgumentException(
113
                'Can not get dynamic template: var name is not a string.'
114
            );
115
        }
116
117
        if (!isset($this->dynamicTemplates[$varName])) {
118
            return '';
119
        }
120
121
        return $this->dynamicTemplates[$varName];
122
    }
123
124
    /**
125
     * @deprecated $GLOBALS['widget_template']
126
     *
127
     * @param  string      $varName       The name of the variable to set this template unto.
128
     * @param  string|null $templateIdent The "dynamic template" to set or NULL to clear.
129
     * @throws InvalidArgumentException If var name is not a string
130
     *     or if the template is not a string (and not null).
131
     * @return void
132
     */
133
    public function setDynamicTemplate($varName, $templateIdent)
134
    {
135
        if (!is_string($varName)) {
136
            throw new InvalidArgumentException(
137
                'Can not set dynamic template: var name is not a string.'
138
            );
139
        }
140
141
        if ($templateIdent === null) {
142
            $this->removeDynamicTemplate($varName);
0 ignored issues
show
Deprecated Code introduced by
The method Charcoal\View\AbstractLo...removeDynamicTemplate() has been deprecated with message: $GLOBALS['widget_template']

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
143
            return;
144
        }
145
146
        if (!is_string($templateIdent)) {
147
            throw new InvalidArgumentException(
148
                'Can not set dynamic template. Must be a a string, or null.'
149
            );
150
        }
151
152
        // Legacy dynamic template hack
153
        if ($varName === 'widget_template') {
154
            $GLOBALS['widget_template'] = $templateIdent;
155
        }
156
157
        $this->dynamicTemplates[$varName] = $templateIdent;
158
    }
159
160
    /**
161
     * @deprecated $GLOBALS['widget_template']
162
     *
163
     * @param  string $varName The name of the variable to remove.
164
     * @throws InvalidArgumentException If var name is not a string.
165
     * @return void
166
     */
167
    public function removeDynamicTemplate($varName)
168
    {
169
        if (!is_string($varName)) {
170
            throw new InvalidArgumentException(
171
                'Can not set dynamic template: var name is not a string.'
172
            );
173
        }
174
175
        // Legacy dynamic template hack
176
        if ($varName === 'widget_template') {
177
            $GLOBALS['widget_template'] = null;
178
        }
179
180
        unset($this->dynamicTemplates[$varName]);
181
    }
182
183
    /**
184
     * @deprecated $GLOBALS['widget_template']
185
     *
186
     * @return void
187
     */
188
    public function clearDynamicTemplates()
189
    {
190
        // Legacy dynamic template hack
191
        $GLOBALS['widget_template'] = null;
192
193
        $this->dynamicTemplates = [];
194
    }
195
196
    /**
197
     * @return string
198
     */
199
    protected function basePath()
200
    {
201
        return $this->basePath;
202
    }
203
204
    /**
205
     * @param  string $basePath The base path to set.
206
     * @throws InvalidArgumentException If the base path parameter is not a string.
207
     * @return LoaderInterface Chainable
208
     */
209
    private function setBasePath($basePath)
210
    {
211
        if (!is_string($basePath)) {
212
            throw new InvalidArgumentException(
213
                'Base path must be a string'
214
            );
215
        }
216
        $basePath = realpath($basePath);
217
        $this->basePath = rtrim($basePath, '/\\').DIRECTORY_SEPARATOR;
218
        return $this;
219
    }
220
221
    /**
222
     * @return string[]
223
     */
224
    protected function paths()
225
    {
226
        return $this->paths;
227
    }
228
229
    /**
230
     * @param  string[] $paths The list of path to add.
231
     * @return LoaderInterface Chainable
232
     */
233
    private function setPaths(array $paths)
234
    {
235
        $this->paths = [];
236
237
        foreach ($paths as $path) {
238
            $this->addPath($path);
239
        }
240
241
        return $this;
242
    }
243
244
    /**
245
     * @param  string $path The path to add to the load.
246
     * @return LoaderInterface Chainable
247
     */
248
    private function addPath($path)
249
    {
250
        $this->paths[] = $this->resolvePath($path);
251
252
        return $this;
253
    }
254
255
    /**
256
     * @param  string $path The path to resolve.
257
     * @throws InvalidArgumentException If the path argument is not a string.
258
     * @return string
259
     */
260
    private function resolvePath($path)
261
    {
262
        if (!is_string($path)) {
263
            throw new InvalidArgumentException(
264
                'Path needs to be a string'
265
            );
266
        }
267
268
        $basePath = $this->basePath();
269
        $path = rtrim($path, '/\\').DIRECTORY_SEPARATOR;
270
        if ($basePath && strpos($path, $basePath) === false) {
271
            $path = $basePath.$path;
272
        }
273
274
        return $path;
275
    }
276
277
    /**
278
     * Get the template file (full path + filename) to load from an ident.
279
     *
280
     * This method first generates the filename for an identifier and search for it in all of the loader's paths.
281
     *
282
     * @param  string $ident The template identifier to load.
283
     * @throws InvalidArgumentException If the template ident is not a string.
284
     * @return string|null The full path + filename of the found template. NULL if nothing was found.
285
     */
286
    protected function findTemplateFile($ident)
287
    {
288
        $key = hash('sha256', $ident, false);
289
290
        if (array_key_exists($key, $this->fileCache)) {
291
            return $this->fileCache[$key];
292
        }
293
294
        if (!is_string($ident)) {
295
            throw new InvalidArgumentException(sprintf(
296
                'Template ident must be a string, received %s',
297
                is_object($ident) ? get_class($ident) : gettype($ident)
298
            ));
299
        }
300
301
        $filename    = $this->filenameFromIdent($ident);
302
        $searchPaths = $this->paths();
303
        foreach ($searchPaths as $searchPath) {
304
            $filepath = realpath($searchPath).'/'.strtolower($filename);
305
            if (file_exists($filepath)) {
306
                $this->fileCache[$key] = $filepath;
307
                return $filepath;
308
            }
309
        }
310
311
        $filepath = null;
312
        $this->fileCache[$key] = $filepath;
313
        return $filepath;
314
    }
315
316
    /**
317
     * @param  string $ident The template identifier to convert to a filename.
318
     * @return string
319
     */
320
    abstract protected function filenameFromIdent($ident);
321
}
322