Test Failed
Push — main ( e1affe...2e6d72 )
by Dimitri
14:06
created

View::first()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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