AbstractLoader   A
last analyzed

Complexity

Total Complexity 26

Size/Duplication

Total Lines 231
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 26
lcom 1
cbo 0
dl 0
loc 231
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A load() 0 22 5
A dynamicTemplate() 0 8 2
A setDynamicTemplate() 0 9 2
A removeDynamicTemplate() 0 4 1
A clearDynamicTemplates() 0 4 1
A basePath() 0 4 1
A setBasePath() 0 6 1
A paths() 0 4 1
A setPaths() 0 10 2
A addPath() 0 6 1
A resolvePath() 0 10 3
A isTemplateString() 0 4 1
A findTemplateFile() 0 22 4
filenameFromIdent() 0 1 ?
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Charcoal\View;
6
7
/**
8
 * Base Template Loader
9
 *
10
 * Finds a template file in a collection of directory paths.
11
 */
12
abstract class AbstractLoader implements LoaderInterface
13
{
14
    /**
15
     * @var string
16
     */
17
    private $basePath = '';
18
19
    /**
20
     * @var string[]
21
     */
22
    private $paths = [];
23
24
    /**
25
     * @var array
26
     */
27
    private $dynamicTemplates = [];
28
29
    /**
30
     * The cache of searched template files.
31
     *
32
     * @var array
33
     */
34
    private $fileCache = [];
35
36
    /**
37
     * Default constructor, if none is provided by the concrete class implementations.
38
     *
39
     *
40
     * @param array $data The class dependencies map.
41
     */
42
    public function __construct(array $data = null)
43
    {
44
        $this->setBasePath($data['base_path']);
45
        $this->setPaths($data['paths']);
46
    }
47
48
    /**
49
     * Load a template content
50
     *
51
     * @param  string $ident The template ident to load and render.
52
     * @return string
53
     */
54
    public function load($ident)
55
    {
56
        // Handle dynamic template
57
        if (substr($ident, 0, 1) === '$') {
58
            $ident = $this->dynamicTemplate(substr($ident, 1));
59
        }
60
61
        /**
62
         * Prevents the loader from passing a proper template through further
63
         * procedures meant for a template identifier.
64
         */
65
        if ($this->isTemplateString($ident)) {
66
            return $ident;
67
        }
68
69
        $file = $this->findTemplateFile($ident);
70
        if ($file === null || $file === '') {
71
            return $ident;
72
        }
73
74
        return file_get_contents($file);
75
    }
76
77
    /**
78
     * @param  string $varName The name of the variable to get template ident from.
79
     * @return string
80
     */
81
    public function dynamicTemplate(string $varName): string
82
    {
83
        if (!isset($this->dynamicTemplates[$varName])) {
84
            return '';
85
        }
86
87
        return $this->dynamicTemplates[$varName];
88
    }
89
90
    /**
91
     * @param  string      $varName       The name of the variable to set this template unto.
92
     * @param  string|null $templateIdent The "dynamic template" to set or NULL to clear.
93
     *     or if the template is not a string (and not null).
94
     * @return void
95
     */
96
    public function setDynamicTemplate(string $varName, ?string $templateIdent): void
97
    {
98
        if ($templateIdent === null) {
99
            $this->removeDynamicTemplate($varName);
100
            return;
101
        }
102
103
        $this->dynamicTemplates[$varName] = $templateIdent;
104
    }
105
106
    /**
107
     * @param  string $varName The name of the variable to remove.
108
     * @return void
109
     */
110
    public function removeDynamicTemplate(string $varName): void
111
    {
112
        unset($this->dynamicTemplates[$varName]);
113
    }
114
115
    /**
116
     * @return void
117
     */
118
    public function clearDynamicTemplates(): void
119
    {
120
        $this->dynamicTemplates = [];
121
    }
122
123
    /**
124
     * @return string
125
     */
126
    protected function basePath(): string
127
    {
128
        return $this->basePath;
129
    }
130
131
    /**
132
     * @param  string $basePath The base path to set.
133
     * @return self
134
     */
135
    private function setBasePath(string $basePath)
136
    {
137
        $basePath = realpath($basePath);
138
        $this->basePath = rtrim($basePath, '/\\').DIRECTORY_SEPARATOR;
139
        return $this;
140
    }
141
142
    /**
143
     * @return string[]
144
     */
145
    protected function paths(): array
146
    {
147
        return $this->paths;
148
    }
149
150
    /**
151
     * @param  string[] $paths The list of path to add.
152
     * @return self
153
     */
154
    private function setPaths(array $paths)
155
    {
156
        $this->paths = [];
157
158
        foreach ($paths as $path) {
159
            $this->addPath($path);
160
        }
161
162
        return $this;
163
    }
164
165
    /**
166
     * @param  string $path The path to add to the load.
167
     * @return self
168
     */
169
    private function addPath(string $path)
170
    {
171
        $this->paths[] = $this->resolvePath($path);
172
173
        return $this;
174
    }
175
176
    /**
177
     * @param  string $path The path to resolve.
178
     * @return string
179
     */
180
    private function resolvePath(string $path): string
181
    {
182
        $basePath = $this->basePath();
183
        $path = rtrim($path, '/\\').DIRECTORY_SEPARATOR;
184
        if ($basePath && strpos($path, $basePath) === false) {
185
            $path = $basePath.$path;
186
        }
187
188
        return $path;
189
    }
190
191
    /**
192
     * Determine if the variable is a template literal.
193
     *
194
     * This method looks for any line-breaks in the given string,
195
     * which a file path would not allow.
196
     *
197
     * @param  string $ident The template being evaluated.
198
     * @return boolean Returns TRUE if the given value is most likely the template contents
199
     *     as opposed to a template identifier (file path).
200
     */
201
    protected function isTemplateString(string $ident): bool
202
    {
203
        return strpos($ident, PHP_EOL) !== false;
204
    }
205
206
    /**
207
     * Get the template file (full path + filename) to load from an ident.
208
     *
209
     * This method first generates the filename for an identifier and search for it in all of the loader's paths.
210
     *
211
     * @param  string $ident The template identifier to load..
212
     * @return string|null The full path + filename of the found template. NULL if nothing was found.
213
     */
214
    protected function findTemplateFile(string $ident): ?string
215
    {
216
        $key = hash('md5', $ident);
217
218
        if (array_key_exists($key, $this->fileCache)) {
219
            return $this->fileCache[$key];
220
        }
221
222
        $filename    = $this->filenameFromIdent($ident);
223
        $searchPaths = $this->paths();
224
        foreach ($searchPaths as $searchPath) {
225
            $filepath = realpath($searchPath).'/'.strtolower($filename);
226
            if (file_exists($filepath)) {
227
                $this->fileCache[$key] = $filepath;
228
                return $filepath;
229
            }
230
        }
231
232
        $filepath = null;
233
        $this->fileCache[$key] = $filepath;
234
        return $filepath;
235
    }
236
237
    /**
238
     * @param  string $ident The template identifier to convert to a filename.
239
     * @return string
240
     */
241
    abstract protected function filenameFromIdent(string $ident): string;
242
}
243