View::setValidationErrors()   A
last analyzed

Complexity

Conditions 5
Paths 9

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 4
Bugs 1 Features 0
Metric Value
cc 5
eloc 15
nc 9
nop 0
dl 0
loc 28
ccs 0
cts 9
cp 0
crap 30
rs 9.4555
c 4
b 1
f 0
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\Contracts\View\RendererInterface;
15
use BlitzPHP\Exceptions\ConfigException;
16
use BlitzPHP\Exceptions\ViewException;
17
use BlitzPHP\Session\Store;
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 object $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
    private ?string $view = null;
64
65
    /**
66
     * Données partagées à toutes les vues
67
     */
68
    private static array $shared = [];
69
70
    /**
71
     * Constructeur
72
     */
73
    public function __construct()
74
    {
75 2
        $this->config = config('view');
76 2
        static::share($this->config['shared']);
77 2
        $this->setAdapter($this->config['active_adapter'] ?? 'native');
78
    }
79
80
    /**
81
     * {@inheritDoc}
82
     */
83
    public function __toString(): string
84
    {
85
        return $this->get();
86
    }
87
88
    /**
89
     * Defini les données partagées entre plusieurs vues
90
     */
91
    public static function share(array|Closure|string $key, mixed $value = null): void
92
    {
93
        if ($key instanceof Closure) {
0 ignored issues
show
introduced by
$key is never a sub-type of Closure.
Loading history...
94 2
            $key = service('container')->call($key);
95
        }
96
        if ($value instanceof Closure) {
97 2
            $value = service('container')->call($value);
98
        }
99
        if (is_string($key)) {
0 ignored issues
show
introduced by
The condition is_string($key) is always false.
Loading history...
100 2
            $key = [$key => $value];
101
        }
102
103 2
        self::$shared = array_merge(self::$shared, $key);
104
    }
105
106
    /**
107
     * Recupere et retourne le code html de la vue créée
108
     */
109
    public function get(bool|string $compress = 'auto', ?bool $saveData = null): string
110
    {
111
        $saveData ??= ($this->options['save_data'] ?? null);
112
113
        $output = $this->adapter->render($this->view, $this->options, $saveData);
114
115
        return $this->compressView($output, $compress);
116
    }
117
118
    /**
119
     * Affiche la vue generee au navigateur
120
     */
121
    public function render(?bool $saveData = null): void
122
    {
123
        $compress = $this->config['compress_output'] ?? 'auto';
124
125
        echo $this->get($compress, $saveData);
126
    }
127
128
    /**
129
     * Construit la sortie en fonction d'une chaîne et de tout données déjà définies.
130
     */
131
    public function renderString(string $view, ?array $options = null, bool $saveData = false): string
132
    {
133
        return $this->adapter->renderString($view, $options, $saveData);
134
    }
135
136
    /**
137
     * Verifie qu'un fichier de vue existe
138
     */
139
    public function exists(string $view, ?string $ext = null, array $options = []): bool
140
    {
141 2
        return $this->adapter->exists($view, $ext, $options);
142
    }
143
144
    /**
145
     * Utilise le premier fichier de vue trouvé pour le rendu
146
     *
147
     * @param list<string> $views
0 ignored issues
show
Bug introduced by
The type BlitzPHP\View\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
148
     */
149
    public function first(array $views, array $data = [], array $options = []): static
150
    {
151
        foreach ($views as $view) {
152
            if ($this->exists($view, null, $options)) {
153
                return $this->make($view, $data, $options);
154
            }
155
        }
156
157
        throw ViewException::invalidFile(implode(' OR ', $views));
158
    }
159
160
    /**
161
     * Crée une instance de vue prêt à être utilisé
162
     */
163
    public function make(string $view, array $data = [], array $options = []): static
164
    {
165
        return $this->addData($data)->options($options)->display($view);
166
    }
167
168
    /**
169
     * Modifier les options d'affichage
170
     *
171
     * @deprecated since 1.0 use options() instead
172
     */
173
    public function setOptions(?array $options = []): static
174
    {
175
        return $this->options($options ?: []);
176
    }
177
178
    /**
179
     * Modifier les options d'affichage
180
     *
181
     * {@internal}
182
     */
183
    public function options(array $options = []): static
184
    {
185
        if (isset($options['layout'])) {
186
            $this->layout($options['layout']);
187
            unset($options['layout']);
188
        }
189
190
        $this->options = $options;
191
192
        return $this;
193
    }
194
195
    /**
196
     * Définir la vue à afficher
197
     *
198
     * {@internal}
199
     */
200
    public function display(string $view, ?array $options = null): static
201
    {
202
        $this->view = $view;
203
204
        if ($options !== null) {
205
            $this->options($options);
206
        }
207
208
        return $this;
209
    }
210
211
    /**
212
     * Définit plusieurs éléments de données de vue à la fois.
213
     *
214
     * {@internal}
215
     */
216
    public function addData(array $data = [], ?string $context = null): static
217
    {
218
        unset($data['errors']);
219
220
        $data = array_merge(self::$shared, $data);
221
222
        if (! on_test() && ! isset($data['errors'])) {
223
            $data['errors'] = $this->setValidationErrors();
224
        }
225
226
        $this->adapter->addData($data, $context);
227
228
        return $this;
229
    }
230
231
    /**
232
     * Définit plusieurs éléments de données de vue à la fois.
233
     */
234
    public function with(array|string $key, mixed $value = null, ?string $context = null): static
235
    {
236
        if (is_array($key)) {
0 ignored issues
show
introduced by
The condition is_array($key) is always true.
Loading history...
237
            $context = $value;
238
        } else {
239
            $key = [$key => $value];
240
        }
241
242
        if (isset($key['errors'])) {
243
            return $this->withErrors($key['errors']);
244
        }
245
246
        return $this->addData($key, $context);
247
    }
248
249
    /**
250
     * Ajoute des erreurs à la session en tant que Flashdata.
251
     */
252
    public function withErrors(array|ErrorBag|string $errors): static
253
    {
254
        if (is_string($errors)) {
0 ignored issues
show
introduced by
The condition is_string($errors) is always false.
Loading history...
255
            $errors = ['default' => $errors];
256
        }
257
        if (! ($errors instanceof ErrorBag)) {
0 ignored issues
show
introduced by
$errors is never a sub-type of BlitzPHP\Validation\ErrorBag.
Loading history...
258
            $errors = new ErrorBag($errors);
259
        }
260
261
        if (isset(self::$shared['errors']) && self::$shared['errors'] instanceof ErrorBag) {
262
            $messages = array_merge(
263
                self::$shared['errors']->toArray(),
264
                $errors->toArray()
265
            );
266
            $errors = new ErrorBag($messages);
267
        }
268
269
        $this->adapter->setVar('errors', $errors);
270
271
        return $this;
272
    }
273
274
    /**
275
     * Définit une seule donnée de vue.
276
     *
277
     * @param mixed|null $value
278
     */
279
    public function setVar(string $name, $value = null, ?string $context = null): static
280
    {
281
        $this->adapter->setVar($name, $value, $context);
282
283
        return $this;
284
    }
285
286
    /**
287
     * Remplacer toutes les données de vue par de nouvelles données
288
     */
289
    public function setData(array $data, ?string $context = null): static
290
    {
291
        unset($data['errors']);
292
293
        $data = array_merge(self::$shared, $data);
294
295
        if (! on_test() && ! isset($data['errors'])) {
296
            $data['errors'] = $this->setValidationErrors();
297
        }
298
299
        $this->adapter->setData($data, $context);
300
301
        return $this;
302
    }
303
304
    /**
305
     * Renvoie les données actuelles qui seront affichées dans la vue.
306
     */
307
    public function getData(): array
308
    {
309
        $data = $this->adapter->getData();
310
311
        if (on_test() && isset($data['errors'])) {
312
            unset($data['errors']);
313
        }
314
315
        return $data;
316
    }
317
318
    /**
319
     * Supprime toutes les données de vue du système.
320
     */
321
    public function resetData(): static
322
    {
323
        $this->adapter->resetData();
324
325
        return $this;
326
    }
327
328
    /**
329
     * Definit le layout a utiliser par les vues
330
     */
331
    public function layout(string $layout): static
332
    {
333
        $this->adapter->setLayout($layout);
334
335
        return $this;
336
    }
337
338
    /**
339
     * @deprecated 1.0 Please use layout method instead
340
     */
341
    public function setLayout(string $layout): static
342
    {
343
        return $this->layout($layout);
344
    }
345
346
    /**
347
     * Defini l'adapteur à utiliser
348
     *
349
     * {@internal}
350
     */
351
    public function setAdapter(string $adapter, array $config = []): static
352
    {
353
        if (! array_key_exists($adapter, self::$validAdapters)) {
354 2
            $adapter = 'native';
355
        }
356
        if (empty($this->config['adapters']) || ! is_array($this->config['adapters'])) {
357 2
            $this->config['adapters'] = [];
358
        }
359
360 2
        $config = array_merge($this->config['adapters'][$adapter] ?? [], $config);
361
        if ($config === []) {
362 2
            throw ConfigException::viewAdapterConfigNotFound($adapter);
363
        }
364
365 2
        $debug = $this->config['debug'] ?? 'auto';
366
        if ($debug === 'auto') {
367 2
            $debug = on_dev();
368
        }
369
370
        $this->adapter = new self::$validAdapters[$adapter](
371
            $config,
372
            $config['view_path'] ?? null,
373
            $debug
374 2
        );
375
376 2
        return $this;
377
    }
378
379
    /**
380
     * Recupere l'adapter utilisé pour générer les vues
381
     */
382
    public function getAdapter(): RendererInterface
383
    {
384
        return $this->adapter;
385
    }
386
387
    /**
388
     * Renvoie les données de performances qui ont pu être collectées lors de l'exécution.
389
     * Utilisé principalement dans la barre d'outils de débogage.
390
     *
391
     * {@internal}
392
     */
393
    public function getPerformanceData(): array
394
    {
395
        return $this->adapter->getPerformanceData();
396
    }
397
398
    /**
399
     * Compresse le code html d'une vue
400
     */
401
    protected function compressView(string $output, bool|callable|string $compress = 'auto'): string
402
    {
403
        $compress = $compress === 'auto' ? ($this->options['compress_output'] ?? 'auto') : $compress;
404
        $compress = $compress === 'auto' ? ($this->config['compress_output'] ?? 'auto') : $compress;
405
406
        if (is_callable($compress)) {
407
            $compress = service('container')->call($compress);
408
        }
409
410
        if ($compress === 'auto') {
411
            $compress = is_online();
412
        }
413
414
        return true === $compress ? trim(preg_replace('/\s+/', ' ', $output)) : $output;
415
    }
416
417
    /**
418
     * Defini les erreurs de validation pour la vue
419
     */
420
    private function setValidationErrors(): ErrorBag
421
    {
422
        $errors = [];
423
424
        /** @var Store $session */
425
        $session = session();
426
427
        if (null !== $e = $session->getFlashdata('errors')) {
428
            if (is_array($e)) {
0 ignored issues
show
introduced by
The condition is_array($e) is always true.
Loading history...
429
                $errors = array_merge($errors, $e);
430
            } else {
431
                $errors['error'] = $e;
432
            }
433
434
            $session->unmarkFlashdata('errors');
435
        }
436
437
        if (null !== $e = $session->getFlashdata('error')) {
438
            if (is_array($e)) {
0 ignored issues
show
introduced by
The condition is_array($e) is always true.
Loading history...
439
                $errors = array_merge($errors, $e);
440
            } else {
441
                $errors['error'] = $e;
442
            }
443
444
            $session->unmarkFlashdata('error');
445
        }
446
447
        return new ErrorBag($errors);
448
    }
449
}
450