Passed
Push — main ( 79ccdf...097cc9 )
by Dimitri
03:17
created

NativeAdapter::insertIf()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 4
nop 4
dl 0
loc 14
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 RuntimeException;
15
16
/**
17
 * Class View
18
 */
19
class NativeAdapter extends AbstractAdapter
20
{
21
    /**
22
     * Fusionner les données enregistrées et les données utilisateur
23
     */
24
    protected $tempData;
25
26
    /**
27
     * Indique si les données doivent être enregistrées entre les rendus.
28
     *
29
     * @var bool
30
     */
31
    protected $saveData;
32
33
    /**
34
     * Nombre de vues chargées
35
     *
36
     * @var int
37
     */
38
    protected $viewsCount = 0;
39
40
    /**
41
     * Contient les sections et leurs données.
42
     *
43
     * @var array
44
     */
45
    protected $sections = [];
46
47
    /**
48
     * Le nom de la section actuelle en cours de rendu, le cas échéant.
49
     *
50
     * @var string[]
51
     */
52
    protected $sectionStack = [];
53
54
    /**
55
     * Contient les css charges a partie de la section actuelle en cours de rendu
56
     *
57
     * @var array
58
     */
59
    protected $_styles = [];
60
61
    /**
62
     * Contient les librairies css charges a partie de la section actuelle en cours de rendu
63
     *
64
     * @var array
65
     */
66
    protected $_lib_styles = [];
67
68
    /**
69
     * Contient les scripts js charges a partie de la section actuelle en cours de rendu
70
     *
71
     * @var array
72
     */
73
    protected $_scripts = [];
74
75
    /**
76
     * Contient les scripts js des librairies charges a partie de la section actuelle en cours de rendu
77
     *
78
     * @var array
79
     */
80
    protected $_lib_scripts = [];
81
82
    /**
83
     * {@inheritDoc}
84
     */
85
    public function __construct(protected array $config, $viewPathLocator = null, protected bool $debug = BLITZ_DEBUG)
86
    {
87
        parent::__construct($config, $viewPathLocator, $debug);
88
89
        $this->saveData = (bool) ($config['save_data'] ?? true);
90
    }
91
92
    /**
93
     * {@inheritDoc}
94
     */
95
    public function render(string $view, ?array $options = null, ?bool $saveData = null): string
96
    {
97
        $this->renderVars['start']   = microtime(true);
98
        $this->renderVars['view']    = $view;
99
        $this->renderVars['options'] = $options ?? [];
100
101
        // Stocke les résultats ici donc même si
102
        // plusieurs vues sont appelées dans une vue, ce ne sera pas le cas
103
        // nettoyez-le sauf si nous le voulons.
104
        $saveData ??= $this->saveData;
105
106
        // A-t-il été mis en cache ?
107
        if (isset($this->renderVars['options']['cache'])) {
108
            $cacheName = $this->renderVars['options']['cache_name'] ?? str_replace('.php', '', $this->renderVars['view']);
109
            $cacheName = str_replace(['\\', '/'], '', $cacheName);
110
111
            $this->renderVars['cacheName'] = $cacheName;
112
113
            if ($output = cache($this->renderVars['cacheName'])) {
114
                $this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
0 ignored issues
show
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

114
                $this->logPerformance($this->renderVars['start'], /** @scrutinizer ignore-type */ microtime(true), $this->renderVars['view']);
Loading history...
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

114
                $this->logPerformance(/** @scrutinizer ignore-type */ $this->renderVars['start'], microtime(true), $this->renderVars['view']);
Loading history...
115
116
                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...
117
            }
118
        }
119
120
        $this->renderVars['file'] = $this->getRenderedFile($this->renderVars['options'], $this->renderVars['view'], 'php');
121
122
        // Rendre nos données de vue disponibles pour la vue.
123
        $this->prepareTemplateData($saveData);
124
125
        // Enregistrer les variables actuelles
126
        $renderVars = $this->renderVars;
127
128
        $output = (function (): string {
129
            extract($this->tempData);
130
            ob_start();
131
            include $this->renderVars['file'];
132
133
            return ob_get_clean() ?: '';
134
        })();
135
136
        // Lors de l'utilisation de mises en page, les données ont déjà été stockées
137
        // dans $this->sections, et aucune autre sortie valide
138
        // est autorisé dans $output donc nous allons l'écraser.
139
        if ($this->layout !== null && $this->sectionStack === []) {
140
            $layoutView   = $this->layout;
141
            $this->layout = null;
142
            // Enregistrer les variables actuelles
143
            $renderVars = $this->renderVars;
144
            $output     = $this->render($layoutView, array_merge($options ?? [], ['viewPath' => LAYOUT_PATH]), $saveData);
145
        }
146
147
        $this->logPerformance($this->renderVars['start'], microtime(true), $this->renderVars['view']);
148
149
        if (($this->debug && (! isset($options['debug']) || $options['debug'] === true))) {
150
            // Nettoyer nos noms de chemins pour les rendre un peu plus propres
151
            $this->renderVars['file'] = clean_path($this->renderVars['file']);
152
            $this->renderVars['file'] = ++$this->viewsCount . ' ' . $this->renderVars['file'];
153
154
            $output = '<!-- DEBUG-VIEW START ' . $this->renderVars['file'] . ' -->' . PHP_EOL
155
                . $output . PHP_EOL
156
                . '<!-- DEBUG-VIEW ENDED ' . $this->renderVars['file'] . ' -->' . PHP_EOL;
157
        }
158
159
        // Faut-il mettre en cache ?
160
        if (isset($this->renderVars['options']['cache'])) {
161
            cache()->write($this->renderVars['cacheName'], $output, (int) $this->renderVars['options']['cache']);
162
        }
163
164
        $this->tempData = null;
165
166
        // Récupère les variables actuelles
167
        $this->renderVars = $renderVars;
168
169
        return $output;
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175
    public function renderString(string $view, ?array $options = null, ?bool $saveData = null): string
176
    {
177
        $start = microtime(true);
178
        $saveData ??= $this->saveData;
179
        $this->prepareTemplateData($saveData);
180
181
        $output = (function (string $view): string {
182
            extract($this->tempData);
183
            ob_start();
184
            eval('?>' . $view);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
185
186
            return ob_get_clean() ?: '';
187
        })($view);
188
189
        $this->logPerformance($start, microtime(true), $this->excerpt($view));
0 ignored issues
show
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

189
        $this->logPerformance($start, /** @scrutinizer ignore-type */ microtime(true), $this->excerpt($view));
Loading history...
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

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

423
            $condition = call_user_func(/** @scrutinizer ignore-type */ $condition);
Loading history...
424
        }
425
426
        if (true === $condition) {
427
            return $this->insert($view, $data, $options, $saveData);
428
        }
429
430
        return '';
431
    }
432
433
    /**
434
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires lorsqu'une condition est remplie.
435
     *
436
     * @alias self::insertWhen()
437
     *
438
     * @param mixed $saveData
439
     */
440
    public function includeWhen(bool|callable $condition, string $view, ?array $data = [], ?array $options = null, $saveData = true): string
441
    {
442
        return $this->insertWhen($condition, $view, $data, $options, $saveData);
443
    }
444
445
    /**
446
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires lorsqu'une condition n'est pas remplie.
447
     * 
448
     * @param mixed $saveData
449
     */
450
    public function insertUnless(bool|callable $condition, string $view, ?array $data = [], ?array $options = null, $saveData = true): string
451
    {
452
        if (is_callable($condition)) {
453
            $condition = call_user_func($condition);
0 ignored issues
show
Bug introduced by
It seems like $condition can also be of type boolean; however, parameter $callback of call_user_func() does only seem to accept callable, 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

453
            $condition = call_user_func(/** @scrutinizer ignore-type */ $condition);
Loading history...
454
        }
455
456
        return $this->insertWhen(false === $condition, $view, $data, $options, $saveData);
457
    }
458
459
    /**
460
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires lorsqu'une condition n'est pas remplie.
461
     *
462
     * @alias self::insertUnless()
463
     *
464
     * @param mixed $saveData
465
     */
466
    public function includeUnless(bool|callable $condition, string $view, ?array $data = [], ?array $options = null, $saveData = true): string
467
    {
468
        return $this->insertUnless($condition, $view, $data, $options, $saveData);
469
    }
470
471
    /**
472
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires si elle existe.
473
     *
474
     * @alias self::insertIf()
475
     *
476
     * @param mixed $saveData
477
     */
478
    public function includeIf(string $view, ?array $data = [], ?array $options = null, $saveData = true): string
479
    {
480
        return $this->insertIf($view, $data, $options, $saveData);
481
    }
482
483
    /**
484
     * Utilisé dans les vues de mise en page pour inclure des vues supplémentaires si elle existe.
485
     *
486
     * @param mixed $saveData
487
     */
488
    public function insertIf(string $view, ?array $data = [], ?array $options = null, $saveData = true): string
489
    {
490
        $view = preg_replace('#\.php$#i', '', $view) . '.php';
491
        $view = str_replace(' ', '', $view);
492
493
        if ($view[0] !== '/') {
494
            $view = $this->retrievePartialPath($view);
495
        }
496
497
        if (is_file($view)) {
498
            return $this->addData($data)->render($view, $options, $saveData);
499
        }
500
        
501
        return '';
502
    }
503
504
505
    /**
506
     * Ajoute un fichier css de librairie a la vue
507
     */
508
    public function addLibCss(string ...$src): self
509
    {
510
        foreach ($src as $var) {
511
            if (! in_array($var, $this->_lib_styles, true)) {
512
                $this->_lib_styles[] = $var;
513
            }
514
        }
515
516
        return $this;
517
    }
518
519
    /**
520
     * Ajoute un fichier css a la vue
521
     */
522
    public function addCss(string ...$src): self
523
    {
524
        foreach ($src as $var) {
525
            if (! in_array($var, $this->_styles, true)) {
526
                $this->_styles[] = $var;
527
            }
528
        }
529
530
        return $this;
531
    }
532
533
    /**
534
     * Compile les fichiers de style de l'instance et genere les link:href vers ceux-ci
535
     */
536
    public function stylesBundle(string ...$groups): void
537
    {
538
        $groups     = (array) (empty($groups) ? $this->layout ?? 'default' : $groups);
539
        $lib_styles = $styles = [];
540
541
        foreach ($groups as $group) {
542
            $lib_styles = array_merge(
543
                $lib_styles,
544
                // (array) config('layout.'.$group.'.lib_styles'),
545
                $this->_lib_styles ?? []
546
            );
547
            $styles = array_merge(
548
                $styles,
549
                // (array) config('layout.'.$group.'.styles'),
550
                $this->_styles ?? []
551
            );
552
        }
553
554
        if (! empty($lib_styles)) {
555
            lib_styles(array_unique($lib_styles));
556
        }
557
        if (! empty($styles)) {
558
            styles(array_unique($styles));
559
        }
560
561
        $this->show('css');
562
    }
563
564
    /**
565
     * Ajoute un fichier js de librairie a la vue
566
     */
567
    public function addLibJs(string ...$src): self
568
    {
569
        foreach ($src as $var) {
570
            if (! in_array($var, $this->_lib_scripts, true)) {
571
                $this->_lib_scripts[] = $var;
572
            }
573
        }
574
575
        return $this;
576
    }
577
578
    /**
579
     * Ajoute un fichier js a la vue
580
     */
581
    public function addJs(string ...$src): self
582
    {
583
        foreach ($src as $var) {
584
            if (! in_array($var, $this->_scripts, true)) {
585
                $this->_scripts[] = $var;
586
            }
587
        }
588
589
        return $this;
590
    }
591
592
    /**
593
     * Compile les fichiers de script de l'instance et genere les link:href vers ceux-ci
594
     */
595
    public function scriptsBundle(string ...$groups): void
596
    {
597
        $groups      = (array) (empty($groups) ? $this->layout ?? 'default' : $groups);
598
        $lib_scripts = $scripts = [];
599
600
        foreach ($groups as $group) {
601
            $lib_scripts = array_merge(
602
                $lib_scripts,
603
                // (array) config('layout.'.$group.'.lib_scripts'),
604
                $this->_lib_scripts ?? []
605
            );
606
            $scripts = array_merge(
607
                $scripts,
608
                // (array) config('layout.'.$group.'.scripts'),
609
                $this->_scripts ?? []
610
            );
611
        }
612
613
        if (! empty($lib_scripts)) {
614
            lib_scripts(array_unique($lib_scripts));
615
        }
616
        if (! empty($scripts)) {
617
            scripts(array_unique($scripts));
618
        }
619
620
        $this->show('js');
621
    }
622
623
    protected function prepareTemplateData(bool $saveData): void
624
    {
625
        $this->tempData ??= $this->data;
626
627
        if ($saveData) {
628
            $this->data = $this->tempData;
629
        }
630
    }
631
632
    private function retrievePartialPath(string $view): string
633
    {
634
        $current_dir = pathinfo($this->renderVars['file'] ?? '', PATHINFO_DIRNAME);
635
        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

635
        if (file_exists(rtrim(/** @scrutinizer ignore-type */ $current_dir, DS) . DS . $view)) {
Loading history...
636
            $view = rtrim($current_dir, DS) . DS . $view;
637
        } elseif (file_exists(rtrim($current_dir, DS) . DS . 'partials' . DS . $view)) {
638
            $view = rtrim($current_dir, DS) . DS . 'partials' . DS . $view;
639
        } elseif (file_exists($this->viewPath . 'partials' . DS . $view)) {
640
            $view = $this->viewPath . 'partials' . DS . $view;
641
        } 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

641
        } elseif (file_exists($this->viewPath . trim(dirname(/** @scrutinizer ignore-type */ $current_dir), '/\\') . DS . $view)) {
Loading history...
642
            $view = $this->viewPath . trim(dirname($current_dir), '/\\') . DS . $view;
643
        } elseif (file_exists(VIEW_PATH . 'partials' . DS . $view)) {
644
            $view = VIEW_PATH . 'partials' . DS . $view;
645
        } elseif (file_exists(VIEW_PATH . trim(dirname($current_dir), '/\\') . DS . $view)) {
646
            $view = VIEW_PATH . trim(dirname($current_dir), '/\\') . DS . $view;
647
        }
648
649
        return $view;
650
    }
651
}
652