Test Failed
Push — master ( 72bafb...14ef62 )
by Rafael
04:07
created

TemplateRender   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 415
Duplicated Lines 0 %

Test Coverage

Coverage 50%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 112
c 5
b 0
f 0
dl 0
loc 415
ccs 62
cts 124
cp 0.5
rs 9.0399
wmc 42

22 Methods

Rating   Name   Duplication   Size   Complexity  
A setTwig() 0 4 1
A getSkins() 0 12 3
A getTemplatesFolder() 0 3 1
A getResourceUri() 0 15 4
A setTemplate() 0 4 1
A addVars() 0 3 1
A __construct() 0 17 2
A getTwig() 0 6 1
A addDirs() 0 9 2
A getTemplateVars() 0 3 1
A hasTemplate() 0 3 1
A addExtensions() 0 12 2
A setTemplatesFolder() 0 3 1
A getTemplate() 0 3 2
A getPaths() 0 15 3
A getDefaultValues() 0 3 1
A render() 0 18 4
A getInstance() 0 3 1
A loadPaths() 0 19 3
A getOptions() 0 8 4
A setSkin() 0 6 2
A getTemplatesUri() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TemplateRender often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TemplateRender, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Alxarafe. Development of PHP applications in a flash!
4
 * Copyright (C) 2018-2019 Alxarafe <[email protected]>
5
 */
6
7
namespace Alxarafe\Core\Providers;
8
9
use Alxarafe\Core\Helpers\TwigFilters;
10
use Alxarafe\Core\Helpers\TwigFunctions;
11
use Alxarafe\Core\Helpers\Utils\ClassUtils;
12
use Alxarafe\Core\Helpers\Utils\FileSystemUtils;
13
use Twig\Environment;
14
use Twig\Error\LoaderError;
15
use Twig\Error\RuntimeError;
16
use Twig\Error\SyntaxError;
17
use Twig\Extension\DebugExtension;
18
use Twig\Loader\FilesystemLoader;
19
20
/**
21
 * Class TemplateRender
22
 *
23
 * @package WebApp\Providers
24
 */
25
class TemplateRender
26
{
27
    use Singleton {
28
        getInstance as getInstanceTrait;
29
    }
30
31
    /**
32
     * Folder containing the skins.
33
     * Each subfolder is a skin (configuration that modifies the visual aspect
34
     * of the application)
35
     * Each template will be a folder whose name will be the one that will
36
     * appear in the template selector.
37
     */
38
    public const SKINS_FOLDER = 'resources' . DIRECTORY_SEPARATOR . 'skins';
39
40
    /**
41
     * Folder containing the twig templates and the common css and js code
42
     * reusable by the different skin.
43
     */
44
    public const TEMPLATES_FOLDER = 'resources' . DIRECTORY_SEPARATOR . 'common';
45
46
    /**
47
     * The renderer.
48
     *
49
     * @var Environment
50
     */
51
    protected static $twig;
52
53
    /**
54
     * The template to use.
55
     *
56
     * @var string|null
57
     */
58
    protected static $template;
59
60
    /**
61
     * The skin to use.
62
     *
63
     * @var string|null
64
     */
65
    protected static $skin;
66
67
    /**
68
     * It's the name of the skin that is being used.
69
     *
70
     * @var string
71
     */
72
    private static $currentSkin;
73
74
    /**
75
     * It is the skin, that is, the folder that contains the templates.
76
     *
77
     * It is the folder where the different skins are located. Each skin uses a folder defined by $template, which
78
     * contains the templates that will be used.
79
     *
80
     * @var string
81
     */
82
    private static $templatesFolder;
83
84
    /**
85
     * Array of all templates folders.
86
     *
87
     * @var array
88
     */
89
    private static $templatesFolders;
90
91
    /**
92
     * Contains the template vars.
93
     *
94
     * @var array
95
     */
96
    private static $templateVars;
97
98
    /**
99
     * Template loader from filesystem.
100
     *
101
     * @var FilesystemLoader
102
     */
103
    private static $loader;
104
105
    /**
106
     * TemplateRender constructor.
107
     */
108
    public function __construct()
109
    {
110
        if (!isset(self::$loader)) {
111
            $shortName = ClassUtils::getShortName($this, static::class);
112
            DebugTool::getInstance()->startTimer($shortName, $shortName . ' TemplateRender Constructor');
113
            $this->initSingleton();
114
            self::$template = null;
115
            self::$templateVars = [
116
                '_REQUEST' => $_REQUEST,
117
                '_GET' => $_GET,
118
                '_POST' => $_POST,
119
                'GLOBALS' => $GLOBALS,
120
            ];
121
            $this->setSkin($this->getConfig()['skin'] ?? 'default');
122
            self::$loader = new FilesystemLoader($this->getPaths());
123
            self::$templatesFolders = [];
124
            DebugTool::getInstance()->stopTimer($shortName);
125
        }
126
    }
127
128
    /**
129
     * Set a skin.
130
     *
131
     * @param string $skin
132
     */
133
    public function setSkin(string $skin): void
134
    {
135
        if ($skin !== self::$currentSkin) {
136
            DebugTool::getInstance()->addMessage('messages', 'Established skin ' . $skin);
137
            self::$currentSkin = $skin;
138
            $this->setTemplatesFolder($skin);
139
        }
140
    }
141
142
    /**
143
     * Establish a new template. The parameter must be only de template name, no the path!
144
     *
145
     * @param string $template
146
     */
147
    public function setTemplatesFolder(string $template): void
148
    {
149
        self::$templatesFolder = self::SKINS_FOLDER . DIRECTORY_SEPARATOR . trim($template, DIRECTORY_SEPARATOR);
150
    }
151
152
    /**
153
     * Return the template folder path.
154
     *
155
     * @return string
156
     */
157 118
    public function getTemplatesFolder(): string
158
    {
159 118
        return basePath(self::$templatesFolder);
160
    }
161
162
    /**
163
     * Returns a list of available paths.
164
     *
165
     * @return array
166
     */
167
    public function getPaths(): array
168
    {
169
        $usePath = [];
170
        $paths = [
171
            $this->getTemplatesFolder(),
172
            basePath(self::TEMPLATES_FOLDER),
173
        ];
174
175
        // Only use really existing path
176
        foreach ($paths as $path) {
177
            if (file_exists($path)) {
178
                $usePath[] = $path;
179
            }
180
        }
181
        return $usePath;
182
    }
183
184
    /**
185
     * Return this instance.
186
     *
187
     * @return self
188
     */
189 86
    public static function getInstance(): self
190
    {
191 86
        return self::getInstanceTrait();
192
    }
193
194
    /**
195
     * Return default values
196
     *
197
     * @return array
198
     */
199 5
    public static function getDefaultValues(): array
200
    {
201 5
        return ['skin' => 'default'];
202
    }
203
204
    /**
205
     * Add additional language folders.
206
     *
207
     * @param array $folders
208
     */
209
    public function addDirs(array $folders = []): void
210
    {
211
        $result = [];
212
        foreach ($folders as $key => $folder) {
213
            $result[$folder['name']] = $folder['path'] . DIRECTORY_SEPARATOR . self::TEMPLATES_FOLDER;
214
            FileSystemUtils::mkdir($result[$folder['name']], 0777, true);
215
            DebugTool::getInstance()->addMessage('messages', 'Added template render folder ' . $result[$folder['name']]);
216
        }
217
        self::$templatesFolders = array_merge(self::$templatesFolders, $result);
218
    }
219
220
    /**
221
     * Sets a new twig environment.
222
     *
223
     * @param Environment $twig
224
     *
225
     * @return $this
226
     */
227
    public function setTwig(Environment $twig): self
228
    {
229
        self::$twig = $twig;
230
        return $this;
231
    }
232
233
    /**
234
     * Renders a template.
235
     *
236
     * @param array $data An array of parameters to pass to the template
237
     *
238
     * @return string The rendered template
239
     */
240 85
    public function render(array $data = []): string
241
    {
242
        try {
243 85
            $render = $this->getTwig()->render($this->getTemplate() ?? 'empty.twig', $this->getTemplateVars($data));
244
        } catch (LoaderError $e) {
245
            $render = '';
246
            // When the template cannot be found
247
            Logger::getInstance()::exceptionHandler($e);
248
        } catch (RuntimeError $e) {
249
            $render = '';
250
            // When an error occurred during rendering
251
            Logger::getInstance()::exceptionHandler($e);
252
        } catch (SyntaxError $e) {
253
            $render = '';
254
            // When an error occurred during compilation
255
            Logger::getInstance()::exceptionHandler($e);
256
        }
257 85
        return $render;
258
    }
259
260
    /**
261
     * Return the full twig environtment.
262
     *
263
     * @return Environment
264
     */
265 85
    private function getTwig(): Environment
266
    {
267 85
        self::$twig = new Environment(self::$loader, $this->getOptions());
268 85
        $this->addExtensions();
269 85
        $this->loadPaths();
270 85
        return self::$twig;
271
    }
272
273
    /**
274
     * Returns a list of options.
275
     *
276
     * @return array
277
     */
278 85
    private function getOptions(): array
279
    {
280 85
        $options = [];
281 85
        $options['debug'] = ((defined('DEBUG') && constant('DEBUG')) === true);
282 85
        if (defined('CACHE') && constant('CACHE') === true) {
283 85
            $options['cache'] = basePath(DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'twig');
284
        }
285 85
        return $options;
286
    }
287
288
    /**
289
     * Add extensions to skin render.
290
     *
291
     * @return void
292
     */
293 85
    private function addExtensions(): void
294
    {
295
        // Add support for additional filters
296 85
        self::$twig->addExtension(new TwigFilters());
297
298
        // Add support for additional functions
299 85
        self::$twig->addExtension(new TwigFunctions());
300
301 85
        $isDebug = $this->getOptions()['debug'];
302 85
        if ($isDebug) {
303
            // Only available in debug mode
304
            self::$twig->addExtension(new DebugExtension());
305
        }
306 85
    }
307
308
    /**
309
     * Load paths, including modules.
310
     */
311 85
    private function loadPaths(): void
312
    {
313
        try {
314
            // Adds without namespace
315 85
            self::$loader->addPath($this->getTemplatesFolder());
316 85
            self::$loader->addPath(basePath(self::TEMPLATES_FOLDER));
317
            // Adds with namespace Core
318 85
            self::$loader->addPath($this->getTemplatesFolder(), 'Core');
319 85
            self::$loader->addPath(basePath(self::TEMPLATES_FOLDER), 'Core');
320
321 85
            foreach (self::$templatesFolders as $moduleName => $modulePath) {
322
                FileSystemUtils::mkdir($modulePath, 0777, true);
323
                // Adds without namespace
324
                self::$loader->prependPath($modulePath);
325
                // Adds with namespace Module + $modulePath
326 85
                self::$loader->prependPath($modulePath, 'Module' . $moduleName);
327
            }
328
        } catch (LoaderError $e) {
329
            Logger::getInstance()::exceptionHandler($e);
330
        }
331 85
    }
332
333
    /**
334
     * Return the assigned template to use.
335
     *
336
     * @return string|null
337
     */
338 85
    public function getTemplate(): ?string
339
    {
340 85
        return isset(self::$template) ? self::$template . '.twig' : null;
341
    }
342
343
    /**
344
     * Return a list of template vars, merged with $vars,
345
     *
346
     * @param $vars
347
     *
348
     * @return array
349
     */
350 85
    private function getTemplateVars(array $vars = []): array
351
    {
352 85
        return array_merge($vars, self::$templateVars);
353
    }
354
355
    /**
356
     * Sets the new template to use.
357
     *
358
     * @param string|null $template
359
     *
360
     * @return $this
361
     */
362 78
    public function setTemplate($template): self
363
    {
364 78
        self::$template = $template;
365 78
        return $this;
366
    }
367
368
    /**
369
     * Returns true if a template has been specified.
370
     *
371
     * @return bool
372
     */
373
    public function hasTemplate(): bool
374
    {
375
        return (self::$template !== null);
376
    }
377
378
    /**
379
     * Add vars to template vars.
380
     *
381
     * @param array $vars
382
     */
383 1
    public function addVars(array $vars = []): void
384
    {
385 1
        self::$templateVars = array_merge($vars, self::$templateVars);
386 1
    }
387
388
    /**
389
     * Returns an array with the list of skins (folders inside the folder specified for the templates).
390
     *
391
     * @return array
392
     */
393 5
    public function getSkins(): array
394
    {
395 5
        $path = basePath(self::SKINS_FOLDER);
396 5
        if (!is_dir($path)) {
397
            return [];
398
        }
399 5
        $skins = FileSystemUtils::scandir($path);
400 5
        $ret = [];
401 5
        foreach ($skins as $skin) {
402 5
            $ret[] = $skin->getFilename();
403
        }
404 5
        return $ret;
405
    }
406
407
    /**
408
     * Check different possible locations for the file and return the
409
     * corresponding URI, if it exists.
410
     *
411
     * @param string $path
412
     *
413
     * @return string
414
     */
415 118
    public function getResourceUri(string $path): string
416
    {
417
        $paths = [
418 118
            $this->getTemplatesFolder() . $path => $this->getTemplatesUri() . $path,
419 118
            self::TEMPLATES_FOLDER . $path => baseUrl(self::TEMPLATES_FOLDER . $path),
420 118
            constant('VENDOR_FOLDER') . $path => constant('VENDOR_URI') . $path,
421 118
            basePath($path) => baseUrl($path),
422
        ];
423
424 118
        foreach ($paths as $fullPath => $uriPath) {
425 118
            if (file_exists($fullPath)) {
426 118
                return $uriPath;
427
            }
428
        }
429
        return constant('DEBUG') ? '#' . $path . '#' : '';
430
    }
431
432
    /**
433
     * Return the template folder path from uri.
434
     *
435
     * @return string
436
     */
437 118
    public function getTemplatesUri(): string
438
    {
439 118
        return baseUrl(self::$templatesFolder);
440
    }
441
442
}
443