Passed
Push — main ( ed3749...c0dba5 )
by Dimitri
03:31
created

View::decorate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 11
rs 10
c 0
b 0
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
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
        $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
        static::share($this->config['shared']);
82
        $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|string|Closure $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
            $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
            $key = [$key => $value];
100
        }
101
102
        static::$shared = array_merge(static::$shared, $key);
0 ignored issues
show
Bug introduced by
Since $shared is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $shared to at least protected.
Loading history...
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(static::$shared, $data);
0 ignored issues
show
Bug introduced by
Since $shared is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $shared to at least protected.
Loading history...
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
     * Définit une seule donnée de vue.
182
     *
183
     * @param mixed|null $value
184
     */
185
    public function setVar(string $name, $value = null, ?string $context = null): self
186
    {
187
        $this->adapter->setVar($name, $value, $context);
188
189
        return $this;
190
    }
191
192
    /**
193
     * Remplacer toutes les données de vue par de nouvelles données
194
     */
195
    public function setData(array $data, ?string $context = null): self
196
    {
197
        unset($data['errors']);
198
199
        $data = array_merge(static::$shared, $data);
0 ignored issues
show
Bug introduced by
Since $shared is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $shared to at least protected.
Loading history...
200
201
        $this->adapter->setData($data, $context);
202
203
        if (! array_key_exists('errors', $this->getData())) {
204
            $this->setValidationErrors();
205
        }
206
207
        return $this;
208
    }
209
210
    /**
211
     * Remplacer toutes les données de vue par de nouvelles données
212
     */
213
    public function getData(): array
214
    {
215
        return $this->adapter->getData();
216
    }
217
218
    /**
219
     * Supprime toutes les données de vue du système.
220
     */
221
    public function resetData(): self
222
    {
223
        $this->adapter->resetData();
224
225
        return $this;
226
    }
227
228
    /**
229
     * Definit le layout a utiliser par les vues
230
     */
231
    public function setLayout(string $layout): self
232
    {
233
        $this->adapter->setLayout($layout);
234
235
        return $this;
236
    }
237
238
    /**
239
     * Defini l'adapteur à utiliser
240
     */
241
    public function setAdapter(string $adapter, array $config = []): self
242
    {
243
        if (! array_key_exists($adapter, self::$validAdapters)) {
244
            $adapter = 'native';
245
        }
246
        if (empty($this->config['adapters']) || ! is_array($this->config['adapters'])) {
247
            $this->config['adapters'] = [];
248
        }
249
250
        $config = array_merge($this->config['adapters'][$adapter] ?? [], $config);
251
        if (empty($config)) {
252
            throw ConfigException::viewAdapterConfigNotFound($adapter);
253
        }
254
255
        $debug = $this->config['debug'] ?? 'auto';
256
        if ($debug === 'auto') {
257
            $debug = on_dev();
258
        }
259
260
        $this->adapter = new self::$validAdapters[$adapter](
261
            $config,
262
            $config['view_path_locator'] ?? Services::locator(),
263
            $debug
264
        );
265
266
        return $this;
267
    }
268
269
    /**
270
     * Renvoie les données de performances qui ont pu être collectées
271
     * lors de l'exécution. Utilisé principalement dans la barre d'outils de débogage.
272
     */
273
    public function getPerformanceData(): array
274
    {
275
        return $this->adapter->getPerformanceData();
276
    }
277
278
    /**
279
     * Compresse le code html d'une vue
280
     */
281
    protected function compressView(string $output, bool|callable|string $compress = 'auto'): string
282
    {
283
        $compress = $compress === 'auto' ? ($this->options['compress_output'] ?? 'auto') : $compress;
284
        $compress = $compress === 'auto' ? ($this->config['compress_output'] ?? 'auto') : $compress;
285
286
        if (is_callable($compress)) {
287
            $compress = Services::container()->call($compress);
288
        }
289
        
290
        if ($compress === 'auto') {
291
            $compress = is_online();
292
        }
293
294
        return true === $compress ? trim(preg_replace('/\s+/', ' ', $output)) : $output;
295
    }
296
297
    /**
298
     * Exécute la sortie générée via tous les décorateurs de vue déclarés.
299
     */
300
    protected function decorate(string $output): string
301
    {
302
        foreach ($this->config['decorators'] as $decorator) {
303
            if (!is_subclass_of($decorator, ViewDecoratorInterface::class)) {
304
                throw ViewException::invalidDecorator($decorator);
305
            }
306
307
            $output = $decorator::decorate($output);
308
        }
309
310
        return $output;
311
    }
312
313
    /**
314
     * Defini les erreurs de validation pour la vue
315
     */
316
    private function setValidationErrors()
317
    {
318
        $errors = [];
319
320
        if (null !== $e = session()->getFlashdata('errors')) {
321
            if (is_array($e)) {
0 ignored issues
show
introduced by
The condition is_array($e) is always true.
Loading history...
322
                $errors = array_merge($errors, $e);
323
            } else {
324
                $errors['error'] = $e;
325
            }
326
327
            session()->unmarkFlashdata('errors');
328
        }
329
330
        if (null !== $e = session()->getFlashdata('error')) {
331
            if (is_array($e)) {
0 ignored issues
show
introduced by
The condition is_array($e) is always true.
Loading history...
332
                $errors = array_merge($errors, $e);
333
            } else {
334
                $errors['error'] = $e;
335
            }
336
337
            session()->unmarkFlashdata('error');
338
        }
339
340
        $this->adapter->addData(['errors' => new ErrorBag($errors)]);
341
    }
342
}
343