Passed
Push — main ( beb9e1...43b6a1 )
by Dimitri
25:37 queued 20:48
created

View::layout()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 2
cts 2
cp 1
crap 1
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;
13
14
use BlitzPHP\Container\Services;
15
use BlitzPHP\Contracts\View\RendererInterface;
16
use BlitzPHP\Exceptions\ConfigException;
17
use BlitzPHP\Exceptions\ViewException;
18
use BlitzPHP\Validation\ErrorBag;
19
use BlitzPHP\View\Adapters\AbstractAdapter;
20
use BlitzPHP\View\Adapters\BladeAdapter;
21
use BlitzPHP\View\Adapters\LatteAdapter;
22
use BlitzPHP\View\Adapters\NativeAdapter;
23
use BlitzPHP\View\Adapters\PlatesAdapter;
24
use BlitzPHP\View\Adapters\SmartyAdapter;
25
use BlitzPHP\View\Adapters\TwigAdapter;
26
use Closure;
27
use Stringable;
28
29
class View implements Stringable
30
{
31
    /**
32
     * Views configuration
33
     */
34
    protected array $config = [];
35
36
    /**
37
     * @var RendererInterface
38
     */
39
    private $adapter;
40
41
    /**
42
     * Liste des adapters pris en comptes
43
     *
44
     * @var array<string, class-string<AbstractAdapter>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, class-string<AbstractAdapter>> at position 4 could not be parsed: Unknown type name 'class-string' at position 4 in array<string, class-string<AbstractAdapter>>.
Loading history...
45
     */
46
    public static array $validAdapters = [
47
        'native' => NativeAdapter::class,
48
        'blade'  => BladeAdapter::class,
49
        'latte'  => LatteAdapter::class,
50
        'plates' => PlatesAdapter::class,
51
        'smarty' => SmartyAdapter::class,
52
        'twig'   => TwigAdapter::class,
53
    ];
54
55
    /**
56
     * Options de la vue
57
     */
58
    private array $options = [];
59
60
    /**
61
     * La vue à rendre
62
     *
63
     * @var string
64
     */
65
    private $view;
66
67
    /**
68
     * Données partagées à toutes les vues
69
     */
70
    private static array $shared = [];
71
72
    /**
73
     * Constructeur
74
     */
75
    public function __construct()
76
    {
77 14
        $this->config = config('view');
78 14
        static::share($this->config['shared']);
79 14
        $this->setAdapter($this->config['active_adapter'] ?? 'native');
80
    }
81
82
    /**
83
     * {@inheritDoc}
84
     */
85
    public function __toString(): string
86
    {
87 2
        return $this->get();
88
    }
89
90
    /**
91
     * Defini les données partagées entre plusieurs vues
92
     */
93
    public static function share(array|Closure|string $key, mixed $value = null): void
94
    {
95
        if ($key instanceof Closure) {
0 ignored issues
show
introduced by
$key is never a sub-type of Closure.
Loading history...
96 14
            $key = Services::container()->call($key);
97
        }
98
        if ($value instanceof Closure) {
99 2
            $value = Services::container()->call($value);
100
        }
101
        if (is_string($key)) {
0 ignored issues
show
introduced by
The condition is_string($key) is always false.
Loading history...
102 2
            $key = [$key => $value];
103
        }
104
105 14
        self::$shared = array_merge(self::$shared, $key);
106
    }
107
108
    /**
109
     * Recupere et retourne le code html de la vue créée
110
     */
111
    public function get(bool|string $compress = 'auto', ?bool $saveData = null): string
112
    {
113 8
        $saveData ??= ($this->options['save_data'] ?? null);
114
115 8
        $output = $this->adapter->render($this->view, $this->options, $saveData);
116
117 8
        return $this->compressView($output, $compress);
118
    }
119
120
    /**
121
     * Affiche la vue generee au navigateur
122
     */
123
    public function render(?bool $saveData = null): void
124
    {
125 6
        $compress = $this->config['compress_output'] ?? 'auto';
126
127 6
        echo $this->get($compress, $saveData);
128
    }
129
130
    /**
131
     * Construit la sortie en fonction d'une chaîne et de tout données déjà définies.
132
     */
133
    public function renderString(string $view, ?array $options = null, bool $saveData = false): string
134
    {
135 6
        return $this->adapter->renderString($view, $options, $saveData);
136
    }
137
138
    /**
139
     * Verifie qu'un fichier de vue existe
140
     */
141
    public function exists(string $view, ?string $ext = null, array $options = []): bool
142
    {
143 4
        return $this->adapter->exists($view, $ext, $options);
144
    }
145
146
    /**
147
     * Utilise le premier fichier de vue trouvé pour le rendu
148
     *
149
     * @param string[] $views
150
     */
151
    public function first(array $views, array $data = [], array $options = []): static
152
    {
153
        foreach ($views as $view) {
154
            if ($this->exists($view, null, $options)) {
155 2
                return $this->make($view, $data, $options);
156
            }
157
        }
158
159 2
        throw ViewException::invalidFile(implode(' OR ', $views));
160
    }
161
162
    /**
163
     * Crée une instance de vue prêt à être utilisé
164
     */
165
    public function make(string $view, array $data = [], array $options = []): static
166
    {
167 4
        return $this->addData($data)->options($options)->display($view);
168
    }
169
170
    /**
171
     * Modifier les options d'affichage
172
     *
173
     * @deprecated since 1.0 use options() instead
174
     */
175
    public function setOptions(?array $options = []): static
176
    {
177 2
        return $this->options($options ?: []);
178
    }
179
180
    /**
181
     * Modifier les options d'affichage
182
     *
183
     * {@internal}
184
     */
185
    public function options(array $options = []): static
186
    {
187
        if (isset($options['layout'])) {
188 2
            $this->layout($options['layout']);
189 2
            unset($options['layout']);
190
        }
191
192 6
        $this->options = $options;
193
194 6
        return $this;
195
    }
196
197
    /**
198
     * Définir la vue à afficher
199
     *
200
     * {@internal}
201
     */
202
    public function display(string $view, ?array $options = null): static
203
    {
204 8
        $this->view = $view;
205
206
        if ($options !== null) {
207 2
            $this->options($options);
208
        }
209
210 8
        return $this;
211
    }
212
213
    /**
214
     * Définit plusieurs éléments de données de vue à la fois.
215
     *
216
     * {@internal}
217
     */
218
    public function addData(array $data = [], ?string $context = null): static
219
    {
220 6
        unset($data['errors']);
221
222 6
        $data = array_merge(self::$shared, $data);
223
224
        if (! on_test() && ! isset($data['errors'])) {
225 6
            $data['errors'] = $this->setValidationErrors();
226
        }
227
228 6
        $this->adapter->addData($data, $context);
229
230 6
        return $this;
231
    }
232
233
    /**
234
     * Définit plusieurs éléments de données de vue à la fois.
235
     */
236
    public function with(array|string $key, mixed $value = null, ?string $context = null): static
237
    {
238
        if (is_array($key)) {
0 ignored issues
show
introduced by
The condition is_array($key) is always true.
Loading history...
239 2
            $context = $value;
240
        } else {
241 4
            $key = [$key => $value];
242
        }
243
244
        if (isset($key['errors'])) {
245 2
            return $this->withErrors($key['errors']);
246
        }
247
248 2
        return $this->addData($key, $context);
249
    }
250
251
    /**
252
     * Ajoute des erreurs à la session en tant que Flashdata.
253
     */
254
    public function withErrors(array|ErrorBag|string $errors): static
255
    {
256
        if (is_string($errors)) {
0 ignored issues
show
introduced by
The condition is_string($errors) is always false.
Loading history...
257 2
            $errors = ['default' => $errors];
258
        }
259
        if (! ($errors instanceof ErrorBag)) {
0 ignored issues
show
introduced by
$errors is never a sub-type of BlitzPHP\Validation\ErrorBag.
Loading history...
260 2
            $errors = new ErrorBag($errors);
261
        }
262
263
        if (isset(self::$shared['errors']) && self::$shared['errors'] instanceof ErrorBag) {
264
            $messages = array_merge(
265
                self::$shared['errors']->toArray(),
266
                $errors->toArray()
267 2
            );
268
            $errors = new ErrorBag($messages);
269
        }
270
271 2
        $this->adapter->setVar('errors', $errors);
272
273 2
        return $this;
274
    }
275
276
    /**
277
     * Définit une seule donnée de vue.
278
     *
279
     * @param mixed|null $value
280
     */
281
    public function setVar(string $name, $value = null, ?string $context = null): static
282
    {
283 10
        $this->adapter->setVar($name, $value, $context);
284
285 10
        return $this;
286
    }
287
288
    /**
289
     * Remplacer toutes les données de vue par de nouvelles données
290
     */
291
    public function setData(array $data, ?string $context = null): static
292
    {
293 2
        unset($data['errors']);
294
295 2
        $data = array_merge(self::$shared, $data);
296
297
        if (! on_test() && ! isset($data['errors'])) {
298 2
            $data['errors'] = $this->setValidationErrors();
299
        }
300
301 2
        $this->adapter->setData($data, $context);
302
303 2
        return $this;
304
    }
305
306
    /**
307
     * Renvoie les données actuelles qui seront affichées dans la vue.
308
     */
309
    public function getData(): array
310
    {
311 6
        $data = $this->adapter->getData();
312
313
        if (on_test() && isset($data['errors'])) {
314 6
            unset($data['errors']);
315
        }
316
317 6
        return $data;
318
    }
319
320
    /**
321
     * Supprime toutes les données de vue du système.
322
     */
323
    public function resetData(): static
324
    {
325 2
        $this->adapter->resetData();
326
327 2
        return $this;
328
    }
329
330
    /**
331
     * Definit le layout a utiliser par les vues
332
     */
333
    public function layout(string $layout): static
334
    {
335 2
        $this->adapter->setLayout($layout);
336
337 2
        return $this;
338
    }
339
340
    /**
341
     * @deprecated 1.0 Please use layout method instead
342
     */
343
    public function setLayout(string $layout): static
344
    {
345 2
        return $this->layout($layout);
346
    }
347
348
    /**
349
     * Defini l'adapteur à utiliser
350
     *
351
     * {@internal}
352
     */
353
    public function setAdapter(string $adapter, array $config = []): static
354
    {
355
        if (! array_key_exists($adapter, self::$validAdapters)) {
356 14
            $adapter = 'native';
357
        }
358
        if (empty($this->config['adapters']) || ! is_array($this->config['adapters'])) {
359 14
            $this->config['adapters'] = [];
360
        }
361
362 14
        $config = array_merge($this->config['adapters'][$adapter] ?? [], $config);
363
        if (empty($config)) {
364 2
            throw ConfigException::viewAdapterConfigNotFound($adapter);
365
        }
366
367 14
        $debug = $this->config['debug'] ?? 'auto';
368
        if ($debug === 'auto') {
369 8
            $debug = on_dev();
370
        }
371
372
        $this->adapter = new self::$validAdapters[$adapter](
373
            $config,
374
            $config['view_path_locator'] ?? Services::locator(),
375
            $debug
376 14
        );
377
378 14
        return $this;
379
    }
380
381
    /**
382
     * Recupere l'adapter utilisé pour générer les vues
383
     */
384
    public function getAdapter(): RendererInterface
385
    {
386
        return $this->adapter;
387
    }
388
389
    /**
390
     * Renvoie les données de performances qui ont pu être collectées lors de l'exécution.
391
     * Utilisé principalement dans la barre d'outils de débogage.
392
     *
393
     * {@internal}
394
     */
395
    public function getPerformanceData(): array
396
    {
397 2
        return $this->adapter->getPerformanceData();
398
    }
399
400
    /**
401
     * Compresse le code html d'une vue
402
     */
403
    protected function compressView(string $output, bool|callable|string $compress = 'auto'): string
404
    {
405 8
        $compress = $compress === 'auto' ? ($this->options['compress_output'] ?? 'auto') : $compress;
406 8
        $compress = $compress === 'auto' ? ($this->config['compress_output'] ?? 'auto') : $compress;
407
408
        if (is_callable($compress)) {
409 8
            $compress = Services::container()->call($compress);
0 ignored issues
show
Bug introduced by
It seems like $compress can also be of type boolean; however, parameter $callback of BlitzPHP\Container\Container::call() does only seem to accept array|callable|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

409
            $compress = Services::container()->call(/** @scrutinizer ignore-type */ $compress);
Loading history...
410
        }
411
412
        if ($compress === 'auto') {
413 8
            $compress = is_online();
414
        }
415
416 8
        return true === $compress ? trim(preg_replace('/\s+/', ' ', $output)) : $output;
417
    }
418
419
    /**
420
     * Defini les erreurs de validation pour la vue
421
     */
422
    private function setValidationErrors(): ErrorBag
423
    {
424
        $errors = [];
425
426
        /** @var \BlitzPHP\Session\Store $session */
427
        $session = session();
428
429
        if (null !== $e = $session->getFlashdata('errors')) {
430
            if (is_array($e)) {
0 ignored issues
show
introduced by
The condition is_array($e) is always true.
Loading history...
431
                $errors = array_merge($errors, $e);
432
            } else {
433
                $errors['error'] = $e;
434
            }
435
436
            $session->unmarkFlashdata('errors');
437
        }
438
439
        if (null !== $e = $session->getFlashdata('error')) {
440
            if (is_array($e)) {
0 ignored issues
show
introduced by
The condition is_array($e) is always true.
Loading history...
441
                $errors = array_merge($errors, $e);
442
            } else {
443
                $errors['error'] = $e;
444
            }
445
446
            $session->unmarkFlashdata('error');
447
        }
448
449
        return new ErrorBag($errors);
450
    }
451
}
452