Passed
Push — main ( 66245a...80ccfb )
by Dimitri
12:45 queued 12s
created

NativeAdapter::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 5
rs 10
1
<?php
2
3
/**
4
 * This file is part of Blitz PHP framework.
5
 *
6
 * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace BlitzPHP\View\Adapters;
13
14
use BlitzPHP\Exceptions\ViewException;
15
use RuntimeException;
16
17
/**
18
 * Class View
19
 */
20
class NativeAdapter extends AbstractAdapter
21
{
22
    /**
23
     * Fusionner les données enregistrées et les données utilisateur
24
     */
25
    protected $tempData;
26
27
    /**
28
     * Indique si les données doivent être enregistrées entre les rendus.
29
     *
30
     * @var bool
31
     */
32
    protected $saveData;
33
34
    /**
35
     * Nombre de vues chargées
36
     *
37
     * @var int
38
     */
39
    protected $viewsCount = 0;
40
41
    /**
42
     * Contient les sections et leurs données.
43
     *
44
     * @var array
45
     */
46
    protected $sections = [];
47
48
    /**
49
     * Le nom de la section actuelle en cours de rendu, le cas échéant.
50
     *
51
     * @var string[]
52
     */
53
    protected $sectionStack = [];
54
55
    /**
56
     * Contient les css charges a partie de la section actuelle en cours de rendu
57
     *
58
     * @var array
59
     */
60
    protected $_styles = [];
61
62
    /**
63
     * Contient les librairies css charges a partie de la section actuelle en cours de rendu
64
     *
65
     * @var array
66
     */
67
    protected $_lib_styles = [];
68
69
    /**
70
     * Contient les scripts js charges a partie de la section actuelle en cours de rendu
71
     *
72
     * @var array
73
     */
74
    protected $_scripts = [];
75
76
    /**
77
     * Contient les scripts js des librairies charges a partie de la section actuelle en cours de rendu
78
     *
79
     * @var array
80
     */
81
    protected $_lib_scripts = [];
82
83
    /**
84
     * Constructor.
85
     */
86
    public function __construct(array $config, string $viewPath = VIEW_PATH, ?bool $debug = null)
87
    {
88
        parent::__construct($config, $viewPath, $debug);
89
90
        $this->saveData = (bool) ($config['save_data'] ?? true);
91
    }
92
93
    /**
94
     * {@inheritDoc}
95
     */
96
    public function render(string $view, ?array $options = null, ?bool $saveData = null): string
97
    {
98
        $view = str_replace([$this->viewPath, ' '], '', $view);
99
        if (empty(pathinfo($view, PATHINFO_EXTENSION))) {
100
            $view .= '.' . str_replace('.', '', $this->config['extension'] ?? 'php');
101
        }
102
103
        $this->renderVars['start']   = microtime(true);
104
        $this->renderVars['view']    = $view;
105
        $this->renderVars['options'] = $options ?? [];
106
107
        // Stocke les résultats ici donc même si
108
        // plusieurs vues sont appelées dans une vue, ce ne sera pas le cas
109
        // nettoyez-le sauf si nous le voulons.
110
        $saveData ??= $this->saveData;
111
112
        // A-t-il été mis en cache ?
113
        if (isset($this->renderVars['options']['cache'])) {
114
            $cacheName = $this->renderVars['options']['cache_name'] ?? str_replace('.php', '', $this->renderVars['view']);
115
            $cacheName = str_replace(['\\', '/'], '', $cacheName);
116
117
            $this->renderVars['cacheName'] = $cacheName;
118
119
            if ($output = cache($this->renderVars['cacheName'])) {
120
                $this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
0 ignored issues
show
Bug introduced by
It seems like $this->renderVars['start'] can also be of type string; however, parameter $start of BlitzPHP\View\Adapters\A...apter::logPerformance() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
                $this->logPerformance(/** @scrutinizer ignore-type */ $this->renderVars['start'], microtime(true), $this->renderVars['view']);
Loading history...
Bug introduced by
It seems like microtime(true) can also be of type string; however, parameter $end of BlitzPHP\View\Adapters\A...apter::logPerformance() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
                $this->logPerformance($this->renderVars['start'], /** @scrutinizer ignore-type */ microtime(true), $this->renderVars['view']);
Loading history...
121
122
                return $output;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $output returns the type BlitzPHP\Cache\Cache|true which is incompatible with the type-hinted return string.
Loading history...
123
            }
124
        }
125
126
        $this->renderVars['file'] = str_replace('/', DS, rtrim($options['viewPath'] ?? $this->viewPath, '/\\') . DS . ltrim($this->renderVars['view'], '/\\'));
127
128
        if (! is_file($this->renderVars['file'])) {
129
            throw ViewException::invalidFile($this->renderVars['view']);
130
        }
131
132
        // Rendre nos données de vue disponibles pour la vue.
133
        $this->prepareTemplateData($saveData);
134
135
        // Enregistrer les variables actuelles
136
        $renderVars = $this->renderVars;
137
138
        $output = (function (): string {
139
            extract($this->tempData);
140
            ob_start();
141
            include $this->renderVars['file'];
142
143
            return ob_get_clean() ?: '';
144
        })();
145
146
        // Récupère les variables actuelles
147
        $this->renderVars = $renderVars;
148
149
        // Lors de l'utilisation de mises en page, les données ont déjà été stockées
150
        // dans $this->sections, et aucune autre sortie valide
151
        // est autorisé dans $output donc nous allons l'écraser.
152
        if ($this->layout !== null && $this->sectionStack === []) {
153
            $layoutView   = $this->layout;
154
            $this->layout = null;
155
            // Enregistrer les variables actuelles
156
            $renderVars = $this->renderVars;
157
            $output     = $this->render($layoutView, array_merge($options ?? [], ['viewPath' => LAYOUT_PATH]), $saveData);
158
            // Récupère les variables actuelles
159
            $this->renderVars = $renderVars;
160
        }
161
162
        $this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
163
164
        if (($this->debug && (! isset($options['debug']) || $options['debug'] === true))) {
165
            // Nettoyer nos noms de chemins pour les rendre un peu plus propres
166
            $this->renderVars['file'] = clean_path($this->renderVars['file']);
167
            $this->renderVars['file'] = ++$this->viewsCount . ' ' . $this->renderVars['file'];
168
169
            $output = '<!-- DEBUG-VIEW START ' . $this->renderVars['file'] . ' -->' . PHP_EOL
170
                . $output . PHP_EOL
171
                . '<!-- DEBUG-VIEW ENDED ' . $this->renderVars['file'] . ' -->' . PHP_EOL;
172
        }
173
174
        // Faut-il mettre en cache ?
175
        if (isset($this->renderVars['options']['cache'])) {
176
            cache()->write($this->renderVars['cacheName'], $output, (int) $this->renderVars['options']['cache']);
177
        }
178
179
        $this->tempData = null;
180
181
        return $output;
182
    }
183
184
    /**
185
     * {@inheritDoc}
186
     */
187
    public function renderString(string $view, ?array $options = null, ?bool $saveData = null): string
188
    {
189
        $start = microtime(true);
190
        $saveData ??= $this->saveData;
191
        $this->prepareTemplateData($saveData);
192
193
        $output = (function (string $view): string {
194
            extract($this->tempData);
195
            ob_start();
196
            eval('?>' . $view);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
197
198
            return ob_get_clean() ?: '';
199
        })($view);
200
201
        $this->logPerformance($start, microtime(true), $this->excerpt($view));
0 ignored issues
show
Bug introduced by
It seems like $start can also be of type string; however, parameter $start of BlitzPHP\View\Adapters\A...apter::logPerformance() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

201
        $this->logPerformance(/** @scrutinizer ignore-type */ $start, microtime(true), $this->excerpt($view));
Loading history...
Bug introduced by
It seems like microtime(true) can also be of type string; however, parameter $end of BlitzPHP\View\Adapters\A...apter::logPerformance() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

201
        $this->logPerformance($start, /** @scrutinizer ignore-type */ microtime(true), $this->excerpt($view));
Loading history...
202
        $this->tempData = null;
203
204
        return $output;
205
    }
206
207
    /**
208
     * Extraire le premier bit d'une longue chaîne et ajouter des points de suspension
209
     */
210
    public function excerpt(string $string, int $length = 20): string
211
    {
212
        return (strlen($string) > $length) ? substr($string, 0, $length - 3) . '...' : $string;
213
    }
214
215
    /**
216
     * {@inheritDoc}
217
     */
218
    public function setData(array $data = [], ?string $context = null): self
219
    {
220
        if ($context) {
221
            $data = esc($data, $context);
222
        }
223
224
        $this->tempData = $data;
225
226
        return $this;
227
    }
228
229
    /**
230
     * {@inheritDoc}
231
     */
232
    public function addData(array $data = [], ?string $context = null): self
233
    {
234
        if ($context) {
235
            $data = esc($data, $context);
236
        }
237
238
        $this->tempData ??= $this->data;
239
        $this->tempData = array_merge($this->tempData, $data);
240
241
        return $this;
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247
    public function setVar(string $name, $value = null, ?string $context = null): self
248
    {
249
        if ($context) {
250
            $value = esc($value, $context);
251
        }
252
253
        $this->tempData ??= $this->data;
254
        $this->tempData[$name] = $value;
255
256
        return $this;
257
    }
258
259
    /**
260
     * {@inheritDoc}
261
     */
262
    public function getData(): array
263
    {
264
        return $this->tempData ?? $this->data;
265
    }
266
267
    /**
268
     * Spécifie que la vue actuelle doit étendre une mise en page existante.
269
     */
270
    public function extend(string $layout)
271
    {
272
        $this->layout = $layout;
273
    }
274
275
    /**
276
     * Commence contient le contenu d'une section dans la mise en page.
277
     */
278
    public function start(string $name)
279
    {
280
        $this->sectionStack[] = $name;
281
282
        ob_start();
283
    }
284
285
    /**
286
     * Commence contient le contenu d'une section dans la mise en page.
287
     *
288
     * @alias self::start()
289
     */
290
    public function section(string $name): void
291
    {
292
        $this->start($name);
293
    }
294
295
    /**
296
     * Commence contient le contenu d'une section dans la mise en page.
297
     *
298
     * @alias self::start()
299
     */
300
    public function begin(string $name): void
301
    {
302
        $this->start($name);
303
    }
304
305
    /**
306
     * Capture la dernière section
307
     *
308
     * @throws RuntimeException
309
     */
310
    public function stop()
311
    {
312
        $contents = ob_get_clean();
313
314
        if ($this->sectionStack === []) {
315
            throw new RuntimeException('View themes, no current section.');
316
        }
317
318
        $section = array_pop($this->sectionStack);
319
320
        // Assurez-vous qu'un tableau existe afin que nous puissions stocker plusieurs entrées pour cela.
321
        if (! array_key_exists($section, $this->sections)) {
322
            $this->sections[$section] = [];
323
        }
324
325
        $this->sections[$section][] = $contents;
326
    }
327
328
    /**
329
     * Capture la dernière section
330
     *
331
     * @throws RuntimeException
332
     *
333
     * @alias self::stop()
334
     */
335
    public function endSection(): void
336
    {
337
        $this->stop();
338
    }
339
340
    /**
341
     * Capture la dernière section
342
     *
343
     * @throws RuntimeException
344
     *
345
     * @alias self::stop()
346
     */
347
    public function end(): void
348
    {
349
        $this->stop();
350
    }
351
352
    /**
353
     * Restitue le contenu d'une section.
354
     */
355
    public function show(string $sectionName)
356
    {
357
        if (! isset($this->sections[$sectionName])) {
358
            echo '';
359
360
            return;
361
        }
362
363
        $start = $end = '';
364
        if ($sectionName === 'css') {
365
            $start = "<style type=\"text/css\">\n";
366
            $end   = "</style>\n";
367
        }
368
        if ($sectionName === 'js') {
369
            $start = "<script type=\"text/javascript\">\n";
370
            $end   = "</script>\n";
371
        }
372
373
        echo $start;
374
375
        foreach ($this->sections[$sectionName] as $key => $contents) {
376
            echo $contents;
377
            unset($this->sections[$sectionName][$key]);
378
        }
379
380
        echo $end;
381
    }
382
383
    /**
384
     * Affichage rapide du contenu principal
385
     */
386
    public function renderView(): void
387
    {
388
        $this->show('content');
389
    }
390
391
    /**
392
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires.
393
     *
394
     * @param mixed $saveData
395
     */
396
    public function insert(string $view, ?array $data = [], ?array $options = null, $saveData = true): string
397
    {
398
        $view = preg_replace('#\.php$#i', '', $view) . '.php';
399
        $view = str_replace(' ', '', $view);
400
401
        if ($view[0] !== '/') {
402
            $current_dir = pathinfo($this->renderVars['file'] ?? '', PATHINFO_DIRNAME);
403
            if (file_exists(rtrim($current_dir, DS) . DS . $view)) {
0 ignored issues
show
Bug introduced by
It seems like $current_dir can also be of type array; however, parameter $string of rtrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

403
            if (file_exists(rtrim(/** @scrutinizer ignore-type */ $current_dir, DS) . DS . $view)) {
Loading history...
404
                $view = rtrim($current_dir, DS) . DS . $view;
405
            } elseif (file_exists($this->viewPath . 'partials' . DS . $view)) {
406
                $view = $this->viewPath . 'partials' . DS . $view;
407
            } elseif (file_exists($this->viewPath . trim(dirname($current_dir), '/\\') . DS . $view)) {
0 ignored issues
show
Bug introduced by
It seems like $current_dir can also be of type array; however, parameter $path of dirname() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

407
            } elseif (file_exists($this->viewPath . trim(dirname(/** @scrutinizer ignore-type */ $current_dir), '/\\') . DS . $view)) {
Loading history...
408
                $view = $this->viewPath . trim(dirname($current_dir), '/\\') . DS . $view;
409
            } elseif (file_exists(VIEW_PATH . 'partials' . DS . $view)) {
410
                $view = VIEW_PATH . 'partials' . DS . $view;
411
            } elseif (file_exists(VIEW_PATH . trim(dirname($current_dir), '/\\') . DS . $view)) {
412
                $view = VIEW_PATH . trim(dirname($current_dir), '/\\') . DS . $view;
413
            }
414
        }
415
416
        return $this->addData($data)->render($view, $options, $saveData);
417
    }
418
419
    /**
420
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires.
421
     *
422
     * @alias self::insert()
423
     *
424
     * @param mixed $saveData
425
     */
426
    public function include(string $view, ?array $data = [], ?array $options = null, $saveData = true): string
427
    {
428
        return $this->insert($view, $data, $options, $saveData);
429
    }
430
431
    /**
432
     * Ajoute un fichier css de librairie a la vue
433
     */
434
    public function addLibCss(string ...$src): self
435
    {
436
        foreach ($src as $var) {
437
            if (! in_array($var, $this->_lib_styles, true)) {
438
                $this->_lib_styles[] = $var;
439
            }
440
        }
441
442
        return $this;
443
    }
444
445
    /**
446
     * Ajoute un fichier css a la vue
447
     */
448
    public function addCss(string ...$src): self
449
    {
450
        foreach ($src as $var) {
451
            if (! in_array($var, $this->_styles, true)) {
452
                $this->_styles[] = $var;
453
            }
454
        }
455
456
        return $this;
457
    }
458
459
    /**
460
     * Compile les fichiers de style de l'instance et genere les link:href vers ceux-ci
461
     */
462
    public function stylesBundle(string ...$groups): void
463
    {
464
        $groups     = (array) (empty($groups) ? $this->layout ?? 'default' : $groups);
465
        $lib_styles = $styles = [];
466
467
        foreach ($groups as $group) {
468
            $lib_styles = array_merge(
469
                $lib_styles,
470
                // (array) config('layout.'.$group.'.lib_styles'),
471
                $this->_lib_styles ?? []
472
            );
473
            $styles = array_merge(
474
                $styles,
475
                // (array) config('layout.'.$group.'.styles'),
476
                $this->_styles ?? []
477
            );
478
        }
479
480
        if (! empty($lib_styles)) {
481
            lib_styles(array_unique($lib_styles));
482
        }
483
        if (! empty($styles)) {
484
            styles(array_unique($styles));
485
        }
486
487
        $this->show('css');
488
    }
489
490
    /**
491
     * Ajoute un fichier js de librairie a la vue
492
     */
493
    public function addLibJs(string ...$src): self
494
    {
495
        foreach ($src as $var) {
496
            if (! in_array($var, $this->_lib_scripts, true)) {
497
                $this->_lib_scripts[] = $var;
498
            }
499
        }
500
501
        return $this;
502
    }
503
504
    /**
505
     * Ajoute un fichier js a la vue
506
     */
507
    public function addJs(string ...$src): self
508
    {
509
        foreach ($src as $var) {
510
            if (! in_array($var, $this->_scripts, true)) {
511
                $this->_scripts[] = $var;
512
            }
513
        }
514
515
        return $this;
516
    }
517
518
    /**
519
     * Compile les fichiers de script de l'instance et genere les link:href vers ceux-ci
520
     */
521
    public function scriptsBundle(string ...$groups): void
522
    {
523
        $groups      = (array) (empty($groups) ? $this->layout ?? 'default' : $groups);
524
        $lib_scripts = $scripts = [];
525
526
        foreach ($groups as $group) {
527
            $lib_scripts = array_merge(
528
                $lib_scripts,
529
                // (array) config('layout.'.$group.'.lib_scripts'),
530
                $this->_lib_scripts ?? []
531
            );
532
            $scripts = array_merge(
533
                $scripts,
534
                // (array) config('layout.'.$group.'.scripts'),
535
                $this->_scripts ?? []
536
            );
537
        }
538
539
        if (! empty($lib_scripts)) {
540
            lib_scripts(array_unique($lib_scripts));
541
        }
542
        if (! empty($scripts)) {
543
            scripts(array_unique($scripts));
544
        }
545
546
        $this->show('js');
547
    }
548
549
    protected function prepareTemplateData(bool $saveData): void
550
    {
551
        $this->tempData ??= $this->data;
552
553
        if ($saveData) {
554
            $this->data = $this->tempData;
555
        }
556
    }
557
}
558