Passed
Push — main ( cd5116...99c066 )
by Dimitri
12:52
created

View::setValidationErrors()   A

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 3
Bugs 1 Features 0
Metric Value
cc 5
eloc 15
c 3
b 1
f 0
nc 9
nop 0
dl 0
loc 28
ccs 0
cts 9
cp 0
crap 30
rs 9.4555
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
26
class View
27
{
28
    /**
29
     * Views configuration
30
     *
31
     * @var array
32
     */
33
    protected $config;
34
35
    /**
36
     * @var RendererInterface
37
     */
38
    private $adapter;
39
40
    /**
41
     * Liste des adapters pris en comptes
42
     *
43
     * @var array
44
     */
45
    public static $validAdapters = [
46
        'native' => NativeAdapter::class,
47
        'blade'  => BladeAdapter::class,
48
        'latte'  => LatteAdapter::class,
49
        'plates' => PlatesAdapter::class,
50
        'smarty' => SmartyAdapter::class,
51
        'twig'   => TwigAdapter::class,
52
    ];
53
54
    /**
55
     * Options de la vue
56
     *
57
     * @var array
58
     */
59
    private $options = [];
60
61
    /**
62
     * La vue à rendre
63
     *
64
     * @var string
65
     */
66
    private $view;
67
68
    /**
69
     * Données partagées à toutes les vues
70
     *
71
     * @var array
72
     */
73
    private static $shared = [];
74
75
    /**
76
     * Constructeur
77
     */
78
    public function __construct()
79
    {
80 2
        $this->config = config('view');
0 ignored issues
show
Documentation Bug introduced by
It seems like config('view') can also be of type BlitzPHP\Config\Config. However, the property $config is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
81 2
        static::share($this->config['shared']);
82 2
        $this->setAdapter($this->config['active_adapter'] ?? 'native');
83
    }
84
85
    public function __toString()
86
    {
87
        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 2
            $key = Services::container()->call($key);
97
        }
98
        if (is_string($key)) {
0 ignored issues
show
introduced by
The condition is_string($key) is always false.
Loading history...
99 2
            $key = [$key => $value];
100
        }
101
102 2
        self::$shared = array_merge(self::$shared, $key);
103
    }
104
105
    /**
106
     * Recupere et retourne le code html de la vue créée
107
     *
108
     * @param bool|string $compress
109
     */
110
    public function get($compress = 'auto'): string
111
    {
112
        $output = $this->adapter->render($this->view, $this->options);
113
        $output = $this->decorate($output);
114
115
        return $this->compressView($output, $compress);
116
    }
117
118
    /**
119
     * Affiche la vue generee au navigateur
120
     */
121
    public function render(): void
122
    {
123
        $compress = $this->config['compress_output'] ?? 'auto';
124
125
        echo $this->get($compress);
126
    }
127
128
    /**
129
     * Modifier les options d'affichage
130
     */
131
    public function setOptions(?array $options = []): self
132
    {
133
        $this->options = (array) $options;
134
135
        return $this;
136
    }
137
138
    /**
139
     * Définir la vue à afficher
140
     */
141
    public function display(string $view): self
142
    {
143
        $this->view = $view;
144
145
        return $this;
146
    }
147
148
    /**
149
     * Définit plusieurs éléments de données de vue à la fois.
150
     */
151
    public function addData(array $data = [], ?string $context = null): self
152
    {
153
        unset($data['errors']);
154
155
        $data = array_merge(self::$shared, $data);
156
157
        $this->adapter->addData($data, $context);
158
159
        if (! array_key_exists('errors', $this->getData())) {
160
            $this->setValidationErrors();
161
        }
162
163
        return $this;
164
    }
165
166
    /**
167
     * Définit plusieurs éléments de données de vue à la fois.
168
     */
169
    public function with(array|string $key, mixed $value = null, ?string $context = null): self
170
    {
171
        if (is_array($key)) {
0 ignored issues
show
introduced by
The condition is_array($key) is always true.
Loading history...
172
            $context = $value;
173
        } else {
174
            $key = [$key => $value];
175
        }
176
177
        return $this->addData($key, $context);
178
    }
179
180
    /**
181
     * Ajoute des erreurs à la session en tant que Flashdata.
182
     */
183
    public function withErrors(array|ErrorBag|string $errors): static
184
    {
185
        if (is_string($errors)) {
0 ignored issues
show
introduced by
The condition is_string($errors) is always false.
Loading history...
186
            $errors = ['default' => $errors];
187
        }
188
        if (! ($errors instanceof ErrorBag)) {
0 ignored issues
show
introduced by
$errors is never a sub-type of BlitzPHP\Validation\ErrorBag.
Loading history...
189
            $errors = new ErrorBag($errors);
190
        }
191
192
        if (isset(self::$shared['errors']) && self::$shared['errors'] instanceof ErrorBag) {
193
            $messages = array_merge(
194
                self::$shared['errors']->toArray(),
195
                $errors->toArray()
196
            );
197
            $errors = new ErrorBag($messages);
198
        }
199
200
        $this->share('errors', $errors);
201
202
        return $this;
203
    }
204
205
    /**
206
     * Définit une seule donnée de vue.
207
     *
208
     * @param mixed|null $value
209
     */
210
    public function setVar(string $name, $value = null, ?string $context = null): self
211
    {
212
        $this->adapter->setVar($name, $value, $context);
213
214
        return $this;
215
    }
216
217
    /**
218
     * Remplacer toutes les données de vue par de nouvelles données
219
     */
220
    public function setData(array $data, ?string $context = null): self
221
    {
222
        unset($data['errors']);
223
224
        $data = array_merge(self::$shared, $data);
225
226
        $this->adapter->setData($data, $context);
227
228
        if (! array_key_exists('errors', $this->getData())) {
229
            $this->setValidationErrors();
230
        }
231
232
        return $this;
233
    }
234
235
    /**
236
     * Remplacer toutes les données de vue par de nouvelles données
237
     */
238
    public function getData(): array
239
    {
240
        return $this->adapter->getData();
241
    }
242
243
    /**
244
     * Supprime toutes les données de vue du système.
245
     */
246
    public function resetData(): self
247
    {
248
        $this->adapter->resetData();
249
250
        return $this;
251
    }
252
253
    /**
254
     * Definit le layout a utiliser par les vues
255
     */
256
    public function setLayout(string $layout): self
257
    {
258
        $this->adapter->setLayout($layout);
259
260
        return $this;
261
    }
262
263
    /**
264
     * Defini l'adapteur à utiliser
265
     */
266
    public function setAdapter(string $adapter, array $config = []): self
267
    {
268
        if (! array_key_exists($adapter, self::$validAdapters)) {
269 2
            $adapter = 'native';
270
        }
271
        if (empty($this->config['adapters']) || ! is_array($this->config['adapters'])) {
272 2
            $this->config['adapters'] = [];
273
        }
274
275 2
        $config = array_merge($this->config['adapters'][$adapter] ?? [], $config);
276
        if (empty($config)) {
277 2
            throw ConfigException::viewAdapterConfigNotFound($adapter);
278
        }
279
280 2
        $debug = $this->config['debug'] ?? 'auto';
281
        if ($debug === 'auto') {
282 2
            $debug = on_dev();
283
        }
284
285
        $this->adapter = new self::$validAdapters[$adapter](
286
            $config,
287
            $config['view_path_locator'] ?? Services::locator(),
288
            $debug
289 2
        );
290
291 2
        return $this;
292
    }
293
294
    /**
295
     * Renvoie les données de performances qui ont pu être collectées
296
     * lors de l'exécution. Utilisé principalement dans la barre d'outils de débogage.
297
     */
298
    public function getPerformanceData(): array
299
    {
300
        return $this->adapter->getPerformanceData();
301
    }
302
303
    /**
304
     * Compresse le code html d'une vue
305
     */
306
    protected function compressView(string $output, bool|callable|string $compress = 'auto'): string
307
    {
308
        $compress = $compress === 'auto' ? ($this->options['compress_output'] ?? 'auto') : $compress;
309
        $compress = $compress === 'auto' ? ($this->config['compress_output'] ?? 'auto') : $compress;
310
311
        if (is_callable($compress)) {
312
            $compress = Services::container()->call($compress);
313
        }
314
315
        if ($compress === 'auto') {
316
            $compress = is_online();
317
        }
318
319
        return true === $compress ? trim(preg_replace('/\s+/', ' ', $output)) : $output;
320
    }
321
322
    /**
323
     * Exécute la sortie générée via tous les décorateurs de vue déclarés.
324
     */
325
    protected function decorate(string $output): string
326
    {
327
        foreach ($this->config['decorators'] as $decorator) {
328
            if (! is_subclass_of($decorator, ViewDecoratorInterface::class)) {
329
                throw ViewException::invalidDecorator($decorator);
330
            }
331
332
            $output = $decorator::decorate($output);
333
        }
334
335
        return $output;
336
    }
337
338
    /**
339
     * Defini les erreurs de validation pour la vue
340
     */
341
    private function setValidationErrors()
342
    {
343
        $errors = [];
344
345
        /** @var \BlitzPHP\Session\Store $session */
346
        $session = session();
347
348
        if (null !== $e = $session->getFlashdata('errors')) {
349
            if (is_array($e)) {
0 ignored issues
show
introduced by
The condition is_array($e) is always true.
Loading history...
350
                $errors = array_merge($errors, $e);
351
            } else {
352
                $errors['error'] = $e;
353
            }
354
355
            $session->unmarkFlashdata('errors');
356
        }
357
358
        if (null !== $e = $session->getFlashdata('error')) {
359
            if (is_array($e)) {
0 ignored issues
show
introduced by
The condition is_array($e) is always true.
Loading history...
360
                $errors = array_merge($errors, $e);
361
            } else {
362
                $errors['error'] = $e;
363
            }
364
365
            $session->unmarkFlashdata('error');
366
        }
367
368
        $this->adapter->addData(['errors' => new ErrorBag($errors)]);
369
    }
370
}
371