Completed
Branch master (dd05db)
by Mathieu
02:02
created

AbstractLoader::load()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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