Completed
Push — master ( dd05db...9c9db5 )
by Chauncey
06:01 queued 04:02
created

AbstractLoader   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 287
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 34
lcom 1
cbo 1
dl 0
loc 287
rs 9.68
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
filenameFromIdent() 0 1 ?
A __construct() 0 6 1
A load() 0 22 5
A dynamicTemplate() 0 14 3
A setDynamicTemplate() 0 21 4
A removeDynamicTemplate() 0 10 2
A clearDynamicTemplates() 0 4 1
A basePath() 0 4 1
A setBasePath() 0 11 2
A paths() 0 4 1
A setPaths() 0 10 2
A addPath() 0 6 1
A resolvePath() 0 16 4
A isTemplateString() 0 4 1
B findTemplateFile() 0 29 6
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
     * @param  string $ident The template ident to load and render.
66
     * @throws InvalidArgumentException If the dynamic template identifier is not a string.
67
     * @return string
68
     */
69
    public function load($ident)
70
    {
71
        // Handle dynamic template
72
        if (substr($ident, 0, 1) === '$') {
73
            $ident = $this->dynamicTemplate(substr($ident, 1));
74
        }
75
76
        /**
77
         * Prevents the loader from passing a proper template through further
78
         * procedures meant for a template identifier.
79
         */
80
        if ($this->isTemplateString($ident)) {
81
            return $ident;
82
        }
83
84
        $file = $this->findTemplateFile($ident);
85
        if ($file === null || $file === '') {
86
            return $ident;
87
        }
88
89
        return file_get_contents($file);
90
    }
91
92
    /**
93
     * @param  string $varName The name of the variable to get template ident from.
94
     * @throws InvalidArgumentException If the var name is not a string.
95
     * @return string
96
     */
97
    public function dynamicTemplate($varName)
98
    {
99
        if (!is_string($varName)) {
100
            throw new InvalidArgumentException(
101
                'Can not get dynamic template: var name is not a string.'
102
            );
103
        }
104
105
        if (!isset($this->dynamicTemplates[$varName])) {
106
            return '';
107
        }
108
109
        return $this->dynamicTemplates[$varName];
110
    }
111
112
    /**
113
     * @param  string      $varName       The name of the variable to set this template unto.
114
     * @param  string|null $templateIdent The "dynamic template" to set or NULL to clear.
115
     * @throws InvalidArgumentException If var name is not a string
116
     *     or if the template is not a string (and not null).
117
     * @return void
118
     */
119
    public function setDynamicTemplate($varName, $templateIdent)
120
    {
121
        if (!is_string($varName)) {
122
            throw new InvalidArgumentException(
123
                'Can not set dynamic template: var name is not a string.'
124
            );
125
        }
126
127
        if ($templateIdent === null) {
128
            $this->removeDynamicTemplate($varName);
129
            return;
130
        }
131
132
        if (!is_string($templateIdent)) {
133
            throw new InvalidArgumentException(
134
                'Can not set dynamic template. Must be a a string, or null.'
135
            );
136
        }
137
138
        $this->dynamicTemplates[$varName] = $templateIdent;
139
    }
140
141
    /**
142
     * @param  string $varName The name of the variable to remove.
143
     * @throws InvalidArgumentException If var name is not a string.
144
     * @return void
145
     */
146
    public function removeDynamicTemplate($varName)
147
    {
148
        if (!is_string($varName)) {
149
            throw new InvalidArgumentException(
150
                'Can not set dynamic template: var name is not a string.'
151
            );
152
        }
153
154
        unset($this->dynamicTemplates[$varName]);
155
    }
156
157
    /**
158
     * @return void
159
     */
160
    public function clearDynamicTemplates()
161
    {
162
        $this->dynamicTemplates = [];
163
    }
164
165
    /**
166
     * @return string
167
     */
168
    protected function basePath()
169
    {
170
        return $this->basePath;
171
    }
172
173
    /**
174
     * @param  string $basePath The base path to set.
175
     * @throws InvalidArgumentException If the base path parameter is not a string.
176
     * @return LoaderInterface Chainable
177
     */
178
    private function setBasePath($basePath)
179
    {
180
        if (!is_string($basePath)) {
181
            throw new InvalidArgumentException(
182
                'Base path must be a string'
183
            );
184
        }
185
        $basePath = realpath($basePath);
186
        $this->basePath = rtrim($basePath, '/\\').DIRECTORY_SEPARATOR;
187
        return $this;
188
    }
189
190
    /**
191
     * @return string[]
192
     */
193
    protected function paths()
194
    {
195
        return $this->paths;
196
    }
197
198
    /**
199
     * @param  string[] $paths The list of path to add.
200
     * @return LoaderInterface Chainable
201
     */
202
    private function setPaths(array $paths)
203
    {
204
        $this->paths = [];
205
206
        foreach ($paths as $path) {
207
            $this->addPath($path);
208
        }
209
210
        return $this;
211
    }
212
213
    /**
214
     * @param  string $path The path to add to the load.
215
     * @return LoaderInterface Chainable
216
     */
217
    private function addPath($path)
218
    {
219
        $this->paths[] = $this->resolvePath($path);
220
221
        return $this;
222
    }
223
224
    /**
225
     * @param  string $path The path to resolve.
226
     * @throws InvalidArgumentException If the path argument is not a string.
227
     * @return string
228
     */
229
    private function resolvePath($path)
230
    {
231
        if (!is_string($path)) {
232
            throw new InvalidArgumentException(
233
                'Path needs to be a string'
234
            );
235
        }
236
237
        $basePath = $this->basePath();
238
        $path = rtrim($path, '/\\').DIRECTORY_SEPARATOR;
239
        if ($basePath && strpos($path, $basePath) === false) {
240
            $path = $basePath.$path;
241
        }
242
243
        return $path;
244
    }
245
246
    /**
247
     * Determine if the variable is a template literal.
248
     *
249
     * This method looks for any line-breaks in the given string,
250
     * which a file path would not allow.
251
     *
252
     * @param  string $ident The template being evaluated.
253
     * @return boolean Returns TRUE if the given value is most likely the template contents
254
     *     as opposed to a template identifier (file path).
255
     */
256
    protected function isTemplateString($ident)
257
    {
258
        return strpos($ident, PHP_EOL) !== false;
259
    }
260
261
    /**
262
     * Get the template file (full path + filename) to load from an ident.
263
     *
264
     * This method first generates the filename for an identifier and search for it in all of the loader's paths.
265
     *
266
     * @param  string $ident The template identifier to load.
267
     * @throws InvalidArgumentException If the template ident is not a string.
268
     * @return string|null The full path + filename of the found template. NULL if nothing was found.
269
     */
270
    protected function findTemplateFile($ident)
271
    {
272
        if (!is_string($ident)) {
273
            throw new InvalidArgumentException(sprintf(
274
                'Template ident must be a string, received %s',
275
                is_object($ident) ? get_class($ident) : gettype($ident)
276
            ));
277
        }
278
279
        $key = hash('md5', $ident);
280
281
        if (array_key_exists($key, $this->fileCache)) {
282
            return $this->fileCache[$key];
283
        }
284
285
        $filename    = $this->filenameFromIdent($ident);
286
        $searchPaths = $this->paths();
287
        foreach ($searchPaths as $searchPath) {
288
            $filepath = realpath($searchPath).'/'.strtolower($filename);
289
            if (file_exists($filepath)) {
290
                $this->fileCache[$key] = $filepath;
291
                return $filepath;
292
            }
293
        }
294
295
        $filepath = null;
296
        $this->fileCache[$key] = $filepath;
297
        return $filepath;
298
    }
299
300
    /**
301
     * @param  string $ident The template identifier to convert to a filename.
302
     * @return string
303
     */
304
    abstract protected function filenameFromIdent($ident);
305
}
306