Passed
Push — main ( d0ed98...da6fcc )
by Dimitri
13:43
created

ServerRequest::_processGet()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 15
c 0
b 0
f 0
nc 8
nop 2
dl 0
loc 23
ccs 0
cts 11
cp 0
crap 20
rs 9.7666
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\Http;
13
14
use BadMethodCallException;
15
use BlitzPHP\Container\Services;
16
use BlitzPHP\Exceptions\FrameworkException;
17
use BlitzPHP\Exceptions\HttpException;
18
use BlitzPHP\Filesystem\Files\UploadedFile;
19
use BlitzPHP\Session\Cookie\CookieCollection;
20
use BlitzPHP\Session\Store;
21
use BlitzPHP\Utilities\Iterable\Arr;
22
use Closure;
23
use GuzzleHttp\Psr7\ServerRequest as Psr7ServerRequest;
24
use GuzzleHttp\Psr7\Stream;
25
use InvalidArgumentException;
26
use Psr\Http\Message\ServerRequestInterface;
27
use Psr\Http\Message\StreamInterface;
28
use Psr\Http\Message\UploadedFileInterface;
29
use Psr\Http\Message\UriInterface;
30
31
/**
32
 * Une classe qui aide à envelopper les informations de la requête et les détails d'une seule requête.
33
 * Fournit des méthodes couramment utilisées pour effectuer une introspection sur les en-têtes et le corps de la requête.
34
 */
35
class ServerRequest implements ServerRequestInterface
36
{
37
    /**
38
     * Tableau de paramètres analysés à partir de l'URL.
39
     */
40
    protected array $params = [
41
        'plugin'     => null,
42
        'controller' => null,
43
        'action'     => null,
44
        '_ext'       => null,
45
        'pass'       => [],
46
    ];
47
48
    /**
49
     * Tableau de données POST. Contiendra des données de formulaire ainsi que des fichiers téléchargés.
50
     * Dans les requêtes PUT/PATCH/DELETE, cette propriété contiendra les données encodées du formulaire.
51
     */
52
    protected null|array|object $data = [];
53
54
    /**
55
     * Tableau d'arguments de chaîne de requête
56
     */
57
    protected array $query = [];
58
59
    /**
60
     * Tableau de données de cookie.
61
     *
62
     * @var array<string, mixed>
63
     */
64
    protected array $cookies = [];
65
66
    /**
67
     * Tableau de données d'environnement.
68
     *
69
     * @var array<string, mixed>
70
     */
71
    protected array $_environment = [];
72
73
    /**
74
     * Chemin de l'URL de base.
75
     */
76
    protected string $base;
77
78
    /**
79
     * segment de chemin webroot pour la demande.
80
     */
81
    protected string $webroot = '/';
82
83
    /**
84
     * S'il faut faire confiance aux en-têtes HTTP_X définis par la plupart des équilibreurs de charge.
85
     * Défini sur vrai uniquement si votre application s'exécute derrière des équilibreurs de charge/proxies que vous contrôlez.
86
     */
87
    public bool $trustProxy = false;
88
89
    /**
90
     * Liste des proxys de confiance
91
     *
92
     * @var string[]
93
     */
94
    protected array $trustedProxies = [];
95
96
    /**
97
     * Les détecteurs intégrés utilisés avec `is()` peuvent être modifiés avec `addDetector()`.
98
     *
99
     * Il existe plusieurs façons de spécifier un détecteur, voir `addDetector()` pour
100
     * les différents formats et façons de définir des détecteurs.
101
     *
102
     * @var array<array|Closure>
103
     */
104
    protected static array $_detectors = [
105
        'get'     => ['env' => 'REQUEST_METHOD', 'value' => 'GET'],
106
        'post'    => ['env' => 'REQUEST_METHOD', 'value' => 'POST'],
107
        'put'     => ['env' => 'REQUEST_METHOD', 'value' => 'PUT'],
108
        'patch'   => ['env' => 'REQUEST_METHOD', 'value' => 'PATCH'],
109
        'delete'  => ['env' => 'REQUEST_METHOD', 'value' => 'DELETE'],
110
        'head'    => ['env' => 'REQUEST_METHOD', 'value' => 'HEAD'],
111
        'options' => ['env' => 'REQUEST_METHOD', 'value' => 'OPTIONS'],
112
        'https'   => ['env' => 'HTTPS', 'options' => [1, 'on']],
113
        'ssl'     => ['env' => 'HTTPS', 'options' => [1, 'on']],
114
        'ajax'    => ['env' => 'HTTP_X_REQUESTED_WITH', 'value' => 'XMLHttpRequest'],
115
        'json'    => ['accept' => ['application/json'], 'param' => '_ext', 'value' => 'json'],
116
        'xml'     => ['accept' => ['application/xml', 'text/xml'], 'param' => '_ext', 'value' => 'xml'],
117
    ];
118
119
    /**
120
     * Cache d'instance pour les résultats des appels is(something)
121
     *
122
     * @var array<string, bool>
123
     */
124
    protected array $_detectorCache = [];
125
126
    /**
127
     * Flux du corps de la requête. Contient php://input sauf si l'option constructeur `input` est utilisée.
128
     */
129
    protected StreamInterface $stream;
130
131
    /**
132
     * instance Uri
133
     */
134
    protected UriInterface $uri;
135
136
    /**
137
     * Instance d'un objet Session relative à cette requête
138
     */
139
    protected Store $session;
140
141
    /**
142
     * Stockez les attributs supplémentaires attachés à la requête.
143
     *
144
     * @var array<string, mixed>
145
     */
146
    protected array $attributes = [];
147
148
    /**
149
     * Une liste de propriétés émulées par les méthodes d'attribut PSR7.
150
     *
151
     * @var array<string>
152
     */
153
    protected array $emulatedAttributes = ['session', 'flash', 'webroot', 'base', 'params', 'here'];
154
155
    /**
156
     * Tableau de fichiers.
157
     */
158
    protected array $uploadedFiles = [];
159
160
    /**
161
     * La version du protocole HTTP utilisée.
162
     */
163
    protected ?string $protocol = null;
164
165
    /**
166
     * La cible de la requête si elle est remplacée
167
     */
168
    protected ?string $requestTarget = null;
169
170
    /**
171
     * Negotiator
172
     */
173
    protected ?Negotiator $negotiator = null;
174
175
    /**
176
     * Créer un nouvel objet de requête.
177
     *
178
     * Vous pouvez fournir les données sous forme de tableau ou de chaîne. Si tu utilises
179
     * une chaîne, vous ne pouvez fournir que l'URL de la demande. L'utilisation d'un tableau
180
     * vous permettent de fournir les clés suivantes :
181
     *
182
     * - `post` Données POST ou données de chaîne sans requête
183
     * - `query` Données supplémentaires de la chaîne de requête.
184
     * - `files` Fichiers téléchargés dans une structure normalisée, avec chaque feuille une instance de UploadedFileInterface.
185
     * - `cookies` Cookies pour cette demande.
186
     * - `environment` $_SERVER et $_ENV données.
187
     * - `url` L'URL sans le chemin de base de la requête.
188
     * - `uri` L'objet PSR7 UriInterface. Si nul, un sera créé à partir de `url` ou `environment`.
189
     * - `base` L'URL de base de la requête.
190
     * - `webroot` Le répertoire webroot pour la requête.
191
     * - `input` Les données qui proviendraient de php://input ceci est utile pour simuler
192
     * requêtes avec mise, patch ou suppression de données.
193
     * - `session` Une instance d'un objet Session
194
     *
195
     * @param array<string, mixed> $config Un tableau de données de requête avec lequel créer une requête.
196
     */
197
    public function __construct(array $config = [])
198
    {
199
        $config += [
200
            'params'      => $this->params,
201
            'query'       => [],
202
            'post'        => [],
203
            'files'       => [],
204
            'cookies'     => [],
205
            'environment' => [],
206
            'url'         => '',
207
            'uri'         => null,
208
            'base'        => '',
209
            'webroot'     => '',
210
            'input'       => null,
211 27
        ];
212
213 27
        $this->_setConfig($config);
214
    }
215
216
    /**
217
     * Traitez les données de configuration/paramètres dans les propriétés.
218
     *
219
     * @param array<string, mixed> $config
220
     */
221
    protected function _setConfig(array $config): void
222
    {
223
        if (empty($config['session'])) {
224 19
            $config['session'] = Services::session(false);
225
        }
226
227
        if (empty($config['environment']['REQUEST_METHOD'])) {
228 25
            $config['environment']['REQUEST_METHOD'] = $_SERVER['REQUEST_METHOD'] ?? 'GET';
229
        }
230
231 27
        $this->cookies = $config['cookies'];
232
233
        if (isset($config['uri'])) {
234
            if (! $config['uri'] instanceof UriInterface) {
235 2
                throw new FrameworkException('The `uri` key must be an instance of ' . UriInterface::class);
236
            }
237 2
            $uri = $config['uri'];
238
        } else {
239
            if ($config['url'] !== '') {
240 2
                $config = $this->processUrlOption($config);
241 2
                $uri    = new Uri(implode('?', [$config['url'], $config['environment']['QUERY_STRING'] ?? '']));
242
            } elseif (isset($config['environment']['REQUEST_URI'])) {
243 4
                $uri = new Uri($config['environment']['REQUEST_URI']);
244
            } else {
245 21
                $uri = Psr7ServerRequest::getUriFromGlobals();
246
            }
247
        }
248
249
        if (in_array($uri->getHost(), ['localhost', '127.0.0.1'], true)) {
250 21
            $uri = $uri->withHost(parse_url(config('app.base_url'), PHP_URL_HOST));
0 ignored issues
show
Bug introduced by
It seems like config('app.base_url') can also be of type BlitzPHP\Config\Config; however, parameter $url of parse_url() does only seem to accept 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

250
            $uri = $uri->withHost(parse_url(/** @scrutinizer ignore-type */ config('app.base_url'), PHP_URL_HOST));
Loading history...
251
        }
252
253 27
        $this->_environment = $config['environment'];
254
255 27
        $this->uri     = $uri;
256 27
        $this->base    = $config['base'];
257 27
        $this->webroot = $config['webroot'];
258
259
        if (isset($config['input'])) {
260 2
            $stream = new Stream(\GuzzleHttp\Psr7\Utils::tryFopen('php://memory', 'rw'));
261 2
            $stream->write($config['input']);
262 2
            $stream->rewind();
263
        } else {
264 27
            $stream = new Stream(\GuzzleHttp\Psr7\Utils::tryFopen('php://input', 'r'));
265
        }
266 27
        $this->stream = $stream;
267
268 27
        $post = $config['post'];
269
        if (! (is_array($post) || is_object($post) || $post === null)) {
270
            throw new InvalidArgumentException(sprintf(
271
                'La clé `post` doit être un tableau, un objet ou null. On a obtenu `%s` à la place.',
272
                get_debug_type($post)
273 27
            ));
274
        }
275 27
        $this->data          = $post;
276 27
        $this->uploadedFiles = $config['files'];
277 27
        $this->query         = $config['query'];
278 27
        $this->params        = $config['params'];
279 27
        $this->session       = $config['session'];
280
    }
281
282
    /**
283
     * Définissez les variables d'environnement en fonction de l'option `url` pour faciliter la génération d'instance UriInterface.
284
     *
285
     * L'option `query` est également mise à jour en fonction de la chaîne de requête de l'URL.
286
     */
287
    protected function processUrlOption(array $config): array
288
    {
289
        if ($config['url'][0] !== '/') {
290 2
            $config['url'] = '/' . $config['url'];
0 ignored issues
show
Bug introduced by
Are you sure $config['url'] of type array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

290
            $config['url'] = '/' . /** @scrutinizer ignore-type */ $config['url'];
Loading history...
291
        }
292
293
        if (str_contains($config['url'], '?')) {
294 2
            [$config['url'], $config['environment']['QUERY_STRING']] = explode('?', $config['url']);
295
296 2
            parse_str($config['environment']['QUERY_STRING'], $queryArgs);
297 2
            $config['query'] += $queryArgs;
298
        }
299
300 2
        $config['environment']['REQUEST_URI'] = $config['url'];
301
302 2
        return $config;
303
    }
304
305
    /**
306
     * Obtenez le type de contenu utilisé dans cette requête.
307
     */
308
    public function contentType(): ?string
309
    {
310 2
        return $this->getEnv('CONTENT_TYPE') ?: $this->getEnv('HTTP_CONTENT_TYPE');
311
    }
312
313
    /**
314
     * Renvoie l'instance de l'objet Session pour cette requête
315
     */
316
    public function session(): Store
317
    {
318 10
        return $this->session;
319
    }
320
321
    /**
322
     * Obtenez l'adresse IP que le client utilise ou dit qu'il utilise.
323
     */
324
    public function clientIp(): string
325
    {
326
        if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_FOR')) {
327
            $addresses = array_map('trim', explode(',', (string) $this->getEnv('HTTP_X_FORWARDED_FOR')));
328
            $trusted   = (count($this->trustedProxies) > 0);
329
            $n         = count($addresses);
330
331
            if ($trusted) {
332
                $trusted = array_diff($addresses, $this->trustedProxies);
333
                $trusted = (count($trusted) === 1);
334
            }
335
336
            if ($trusted) {
337
                return $addresses[0];
338
            }
339
340
            return $addresses[$n - 1];
341
        }
342
343
        if ($this->trustProxy && $this->getEnv('HTTP_X_REAL_IP')) {
344
            $ipaddr = $this->getEnv('HTTP_X_REAL_IP');
345
        } elseif ($this->trustProxy && $this->getEnv('HTTP_CLIENT_IP')) {
346
            $ipaddr = $this->getEnv('HTTP_CLIENT_IP');
347
        } else {
348
            $ipaddr = $this->getEnv('REMOTE_ADDR');
349
        }
350
351
        return trim((string) $ipaddr);
352
    }
353
354
    /**
355
     * Enregistrer des proxys de confiance
356
     *
357
     * @param string[] $proxies ips liste des proxys de confiance
358
     */
359
    public function setTrustedProxies(array $proxies): void
360
    {
361 2
        $this->trustedProxies = $proxies;
362 2
        $this->trustProxy     = true;
363 2
        $this->uri            = $this->uri->withScheme($this->scheme());
0 ignored issues
show
Bug introduced by
It seems like $this->scheme() can also be of type null; however, parameter $scheme of Psr\Http\Message\UriInterface::withScheme() does only seem to accept 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

363
        $this->uri            = $this->uri->withScheme(/** @scrutinizer ignore-type */ $this->scheme());
Loading history...
364
    }
365
366
    /**
367
     * Obtenez les proxys de confiance
368
     */
369
    public function getTrustedProxies(): array
370
    {
371
        return $this->trustedProxies;
372
    }
373
374
    /**
375
     * Renvoie le référent qui a référé cette requête.
376
     *
377
     * @param bool $local Tentative de renvoi d'une adresse locale.
378
     *                    Les adresses locales ne contiennent pas de noms d'hôtes..
379
     */
380
    public function referer(bool $local = true): ?string
381
    {
382
        $ref = $this->getEnv('HTTP_REFERER');
383
384
        $base = config('app.base_url') . $this->webroot;
0 ignored issues
show
Bug introduced by
Are you sure config('app.base_url') of type BlitzPHP\Config\Config|null can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

384
        $base = /** @scrutinizer ignore-type */ config('app.base_url') . $this->webroot;
Loading history...
385
        if (empty($base) || empty($ref)) {
386
            return null;
387
        }
388
389
        if ($local && str_starts_with($ref, $base)) {
390
            $ref = substr($ref, strlen($base));
391
            if ($ref === '' || str_starts_with($ref, '//')) {
392
                $ref = '/';
393
            }
394
            if ($ref[0] !== '/') {
395
                $ref = '/' . $ref;
396
            }
397
398
            return $ref;
399
        }
400
401
        if ($local) {
402
            return null;
403
        }
404
405
        return $ref;
406
    }
407
408
    /**
409
     * Gestionnaire de méthodes manquant, les poignées enveloppent les anciennes méthodes de type isAjax()
410
     *
411
     * @return bool
412
     *
413
     * @throws BadMethodCallException lorsqu'une méthode invalide est appelée.
414
     */
415
    public function __call(string $name, array $params)
416
    {
417
        if (str_starts_with($name, 'is')) {
418 2
            $type = strtolower(substr($name, 2));
419
420 2
            array_unshift($params, $type);
421
422 2
            return $this->is(...$params);
423
        }
424
425
        throw new BadMethodCallException(sprintf('La méthode "%s()" n\'existe pas', $name));
426
    }
427
428
    /**
429
     * Vérifiez si une demande est d'un certain type.
430
     *
431
     * Utilise les règles de détection intégrées ainsi que des règles supplémentaires
432
     * défini avec {@link \BlitzPHP\Http\ServerRequest::addDetector()}. Tout détecteur peut être appelé
433
     * comme `is($type)` ou `is$Type()`.
434
     *
435
     * @param string|string[] $type Le type de requête que vous souhaitez vérifier. S'il s'agit d'un tableau, cette méthode renverra true si la requête correspond à n'importe quel type.
436
     *
437
     * @return bool Si la demande est du type que vous vérifiez.
438
     */
439
    public function is($type, ...$args): bool
440
    {
441
        if (is_array($type)) {
442
            foreach ($type as $_type) {
443
                if ($this->is($_type)) {
444
                    return true;
445
                }
446
            }
447
448
            return false;
449
        }
450
451 4
        $type = strtolower($type);
452
        if (! isset(static::$_detectors[$type])) {
453
            return false;
454
        }
455
        if ($args) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $args of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
456 2
            return $this->_is($type, $args);
457
        }
458
459 4
        return $this->_detectorCache[$type] ??= $this->_is($type, $args);
460
    }
461
462
    /**
463
     * Efface le cache du détecteur d'instance, utilisé par la fonction is()
464
     */
465
    public function clearDetectorCache(): void
466
    {
467 10
        $this->_detectorCache = [];
468
    }
469
470
    /**
471
     * Worker pour la fonction publique is()
472
     *
473
     * @param string $type Le type de requête que vous souhaitez vérifier.
474
     * @param array  $args Tableau d'arguments de détecteur personnalisés.
475
     *
476
     * @return bool Si la demande est du type que vous vérifiez.
477
     */
478
    protected function _is(string $type, array $args): bool
479
    {
480 4
        $detect = static::$_detectors[$type];
481
        if ($detect instanceof Closure) {
482 2
            array_unshift($args, $this);
483
484 2
            return $detect(...$args);
485
        }
486
        if (isset($detect['env']) && $this->_environmentDetector($detect)) {
487
            return true;
488
        }
489
        if (isset($detect['header']) && $this->_headerDetector($detect)) {
490 2
            return true;
491
        }
492
        if (isset($detect['accept']) && $this->_acceptHeaderDetector($detect)) {
493 2
            return true;
494
        }
495
496 4
        return (bool) (isset($detect['param']) && $this->_paramDetector($detect));
497
    }
498
499
    /**
500
     * Détecte si un en-tête d'acceptation spécifique est présent.
501
     *
502
     * @param array $detect Tableau d'options du détecteur.
503
     *
504
     * @return bool Si la demande est du type que vous vérifiez.
505
     */
506
    protected function _acceptHeaderDetector(array $detect): bool
507
    {
508 2
        $acceptHeaders = explode(',', (string) $this->getEnv('HTTP_ACCEPT'));
509
510
        foreach ($detect['accept'] as $header) {
511
            if (in_array($header, $acceptHeaders, true)) {
512 2
                return true;
513
            }
514
        }
515
516 2
        return false;
517
    }
518
519
    /**
520
     * Détecte si un en-tête spécifique est présent.
521
     *
522
     * @param array $detect Tableau d'options du détecteur.
523
     *
524
     * @return bool Si la demande est du type que vous vérifiez.
525
     */
526
    protected function _headerDetector(array $detect): bool
527
    {
528
        foreach ($detect['header'] as $header => $value) {
529 2
            $header = $this->getEnv('http_' . $header);
530
            if ($header !== null) {
531
                if ($value instanceof Closure) {
532 2
                    return $value($header);
533
                }
534
535 2
                return $header === $value;
536
            }
537
        }
538
539
        return false;
540
    }
541
542
    /**
543
     * Détecte si un paramètre de requête spécifique est présent.
544
     *
545
     * @param array $detect Tableau d'options du détecteur.
546
     *
547
     * @return bool Si la demande est du type que vous vérifiez.
548
     */
549
    protected function _paramDetector(array $detect): bool
550
    {
551 2
        $key = $detect['param'];
552
        if (isset($detect['value'])) {
553 2
            $value = $detect['value'];
554
555 2
            return isset($this->params[$key]) ? $this->params[$key] === $value : false;
556
        }
557
        if (isset($detect['options'])) {
558
            return isset($this->params[$key]) ? in_array($this->params[$key], $detect['options'], true) : false;
559
        }
560
561
        return false;
562
    }
563
564
    /**
565
     * Détecte si une variable d'environnement spécifique est présente.
566
     *
567
     * @param array $detect Tableau d'options du détecteur.
568
     *
569
     * @return bool Si la demande est du type que vous vérifiez.
570
     */
571
    protected function _environmentDetector(array $detect): bool
572
    {
573
        if (isset($detect['env'])) {
574
            if (isset($detect['value'])) {
575 2
                return $this->getEnv($detect['env']) === $detect['value'];
576
            }
577
            if (isset($detect['pattern'])) {
578
                return (bool) preg_match($detect['pattern'], (string) $this->getEnv($detect['env']));
579
            }
580
            if (isset($detect['options'])) {
581
                $pattern = '/' . implode('|', $detect['options']) . '/i';
582
583
                return (bool) preg_match($pattern, (string) $this->getEnv($detect['env']));
584
            }
585
        }
586
587
        return false;
588
    }
589
590
    /**
591
     * Vérifier qu'une requête correspond à tous les types donnés.
592
     *
593
     * Vous permet de tester plusieurs types et d'unir les résultats.
594
     * Voir Request::is() pour savoir comment ajouter des types supplémentaires et le
595
     * types intégrés.
596
     *
597
     * @param string[] $types Les types à vérifier.
598
     *
599
     * @see \BlitzPHP\Http\ServerRequest::is()
600
     */
601
    public function isAll(array $types): bool
602
    {
603
        foreach ($types as $type) {
604
            if (! $this->is($type)) {
605
                return false;
606
            }
607
        }
608
609
        return true;
610
    }
611
612
    /**
613
     * Ajouter un nouveau détecteur à la liste des détecteurs qu'une requête peut utiliser.
614
     * Il existe plusieurs types de détecteurs différents qui peuvent être réglés.
615
     *
616
     * ### Comparaison des rappels
617
     *
618
     * Les détecteurs de rappel vous permettent de fournir un callable pour gérer le chèque.
619
     * Le rappel recevra l'objet de requête comme seul paramètre.
620
     *
621
     * ```
622
     * addDetector('custom', function ($request) { //Renvoyer un booléen });
623
     * ```
624
     *
625
     * ### Comparaison des valeurs d'environnement
626
     *
627
     * Une comparaison de valeur d'environnement, compare une valeur extraite de `env()` à une valeur connue
628
     * la valeur d'environnement est l'égalité vérifiée par rapport à la valeur fournie.
629
     *
630
     * ```
631
     * addDetector('post', ['env' => 'REQUEST_METHOD', 'value' => 'POST']);
632
     * ```
633
     *
634
     * ### Comparaison des paramètres de demande
635
     *
636
     * Permet des détecteurs personnalisés sur les paramètres de demande.
637
     *
638
     * ```
639
     * addDetector('admin', ['param' => 'prefix', 'value' => 'admin']);
640
     * ```
641
     *
642
     * ### Accepter la comparaison
643
     *
644
     * Permet au détecteur de comparer avec la valeur d'en-tête Accepter.
645
     *
646
     * ```
647
     * addDetector('csv', ['accept' => 'text/csv']);
648
     * ```
649
     *
650
     * ### Comparaison d'en-tête
651
     *
652
     * Permet de comparer un ou plusieurs en-têtes.
653
     *
654
     * ```
655
     * addDetector('fancy', ['header' => ['X-Fancy' => 1]);
656
     * ```
657
     *
658
     * Les types `param`, `env` et de comparaison permettent ce qui suit
659
     * options de comparaison de valeur :
660
     *
661
     * ### Comparaison des valeurs de modèle
662
     *
663
     * La comparaison de valeurs de modèles vous permet de comparer une valeur extraite de `env()` à une expression régulière.
664
     *
665
     * ```
666
     * addDetector('iphone', ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i']);
667
     * ```
668
     *
669
     * ### Comparaison basée sur les options
670
     *
671
     * Les comparaisons basées sur des options utilisent une liste d'options pour créer une expression régulière. Appels ultérieurs
672
     * ajouter un détecteur d'options déjà défini fusionnera les options.
673
     *
674
     * ```
675
     * addDetector('mobile', ['env' => 'HTTP_USER_AGENT', 'options' => ['Fennec']]);
676
     * ```
677
     *
678
     * Vous pouvez également comparer plusieurs valeurs
679
     * en utilisant la touche `options`. Ceci est utile lorsque vous souhaitez vérifier
680
     * si une valeur de requête se trouve dans une liste d'options.
681
     *
682
     * `addDetector('extension', ['param' => '_ext', 'options' => ['pdf', 'csv']]`
683
     *
684
     * @param array|callable $detector Un callback ou tableau d'options pour la définition du détecteur.
685
     */
686
    public static function addDetector(string $name, $detector): void
687
    {
688 2
        $name = strtolower($name);
689
        if ($detector instanceof Closure) {
690 2
            static::$_detectors[$name] = $detector;
691
692 2
            return;
693
        }
694
        if (isset(static::$_detectors[$name], $detector['options'])) {
695
            /** @var array $data */
696
            $data     = static::$_detectors[$name];
697
            $detector = Arr::merge($data, $detector);
698
        }
699 2
        static::$_detectors[$name] = $detector;
700
    }
701
702
    /**
703
     * Normaliser un nom d'en-tête dans la version SERVER.
704
     */
705
    protected function normalizeHeaderName(string $name): string
706
    {
707 16
        $name = str_replace('-', '_', strtoupper($name));
708
        if (! in_array($name, ['CONTENT_LENGTH', 'CONTENT_TYPE'], true)) {
709 16
            $name = 'HTTP_' . $name;
710
        }
711
712 16
        return $name;
713
    }
714
715
    /**
716
     * Obtenez tous les en-têtes de la requête.
717
     *
718
     * Renvoie un tableau associatif où les noms d'en-tête sont
719
     * les clés et les valeurs sont une liste de valeurs d'en-tête.
720
     *
721
     * Bien que les noms d'en-tête ne soient pas sensibles à la casse, getHeaders() normalisera
722
     * les en-têtes.
723
     *
724
     * @return array<string[]> Un tableau associatif d'en-têtes et leurs valeurs.
725
     *
726
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
727
     */
728
    public function getHeaders(): array
729
    {
730
        $headers = [];
731
732
        foreach ($this->_environment as $key => $value) {
733
            $name = null;
734
            if (str_starts_with($key, 'HTTP_')) {
735
                $name = substr($key, 5);
736
            }
737
            if (str_starts_with($key, 'CONTENT_')) {
738
                $name = $key;
739
            }
740
            if ($name !== null) {
741
                $name           = str_replace('_', ' ', strtolower($name));
742
                $name           = str_replace(' ', '-', ucwords($name));
743
                $headers[$name] = (array) $value;
744
            }
745
        }
746
747
        return $headers;
748
    }
749
750
    /**
751
     * Vérifiez si un en-tête est défini dans la requête.
752
     *
753
     * @param string $name L'en-tête que vous souhaitez obtenir (insensible à la casse)
754
     *
755
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
756
     */
757
    public function hasHeader(string $name): bool
758
    {
759
        if (isset($this->_environment[$name])) {
760 2
            return true;
761
        }
762
763
        if (isset($this->_environment[$this->normalizeHeaderName($name)])) {
764 6
            return true;
765
        }
766
767 10
        return [] !== $this->getHeader($name);
768
    }
769
770
    /**
771
     * Obtenez un seul en-tête de la requête.
772
     *
773
     * Renvoie la valeur de l'en-tête sous forme de tableau. Si l'en-tête
774
     * n'est pas présent, un tableau vide sera retourné.
775
     *
776
     * @param string $name L'en-tête que vous souhaitez obtenir (insensible à la casse)
777
     *
778
     * @return string[] Un tableau associatif d'en-têtes et leurs valeurs.
779
     *                  Si l'en-tête n'existe pas, un tableau vide sera retourné.
780
     *
781
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
782
     */
783
    public function getHeader(string $name): array
784
    {
785
        if (isset($this->_environment[$name])) {
786 2
            return (array) $this->_environment[$name];
787
        }
788
789 16
        $name = $this->normalizeHeaderName($name);
790
        if (isset($this->_environment[$name])) {
791 6
            return (array) $this->_environment[$name];
792
        }
793
794 16
        return (array) $this->getEnv($name);
795
    }
796
797
    /**
798
     * Obtenez un seul en-tête sous forme de chaîne à partir de la requête.
799
     *
800
     * @param string $name L'en-tête que vous souhaitez obtenir (insensible à la casse)
801
     *
802
     * @return string Les valeurs d'en-tête sont réduites à une chaîne séparée par des virgules.
803
     *
804
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
805
     */
806
    public function getHeaderLine(string $name): string
807
    {
808 12
        $value = $this->getHeader($name);
809
810 12
        return implode(', ', $value);
811
    }
812
813
    /**
814
     * Obtenez une demande modifiée avec l'en-tête fourni.
815
     *
816
     * @param array|string $value
817
     *
818
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
819
     */
820
    public function withHeader(string $name, $value): static
821
    {
822 2
        $new                      = clone $this;
823 2
        $name                     = $this->normalizeHeaderName($name);
824 2
        $new->_environment[$name] = $value;
825
826 2
        return $new;
827
    }
828
829
    /**
830
     * Obtenez une demande modifiée avec l'en-tête fourni.
831
     *
832
     * Les valeurs d'en-tête existantes seront conservées. La valeur fournie
833
     * sera ajouté aux valeurs existantes.
834
     *
835
     * @param array|string $value
836
     *
837
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
838
     */
839
    public function withAddedHeader(string $name, $value): static
840
    {
841
        $new      = clone $this;
842
        $name     = $this->normalizeHeaderName($name);
843
        $existing = [];
844
        if (isset($new->_environment[$name])) {
845
            $existing = (array) $new->_environment[$name];
846
        }
847
        $existing                 = array_merge($existing, (array) $value);
848
        $new->_environment[$name] = $existing;
849
850
        return $new;
851
    }
852
853
    /**
854
     * Obtenez une demande modifiée sans en-tête fourni.
855
     *
856
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
857
     */
858
    public function withoutHeader(string $name): static
859
    {
860
        $new  = clone $this;
861
        $name = $this->normalizeHeaderName($name);
862
        unset($new->_environment[$name]);
863
864
        return $new;
865
    }
866
867
    /**
868
     * Obtenez la méthode HTTP utilisée pour cette requête.
869
     * Il existe plusieurs manières de spécifier une méthode.
870
     *
871
     * - Si votre client le prend en charge, vous pouvez utiliser des méthodes HTTP natives.
872
     * - Vous pouvez définir l'en-tête HTTP-X-Method-Override.
873
     * - Vous pouvez soumettre une entrée avec le nom `_method`
874
     *
875
     * Chacune de ces 3 approches peut être utilisée pour définir la méthode HTTP utilisée
876
     * par BlitzPHP en interne, et affectera le résultat de cette méthode.
877
     *
878
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
879
     */
880
    public function getMethod(): string
881
    {
882 22
        return (string) $this->getEnv('REQUEST_METHOD', $_SERVER['REQUEST_METHOD'] ?? 'GET');
883
    }
884
885
    /**
886
     * Mettez à jour la méthode de requête et obtenez une nouvelle instance.
887
     *
888
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
889
     */
890
    public function withMethod(string $method): static
891
    {
892 22
        $new = clone $this;
893
894
        if (! preg_match('/^[!#$%&\'*+.^_`\|~0-9a-z-]+$/i', $method)) {
895
            throw new InvalidArgumentException(sprintf(
896
                'Méthode HTTP non prise en charge "%s" fournie',
897
                $method
898 22
            ));
899
        }
900 22
        $new->_environment['REQUEST_METHOD'] = $method;
901
902 22
        return $new;
903
    }
904
905
    /**
906
     * Obtenez tous les paramètres de l'environnement du serveur.
907
     *
908
     * Lire toutes les données 'environnement' ou 'serveur' qui ont été
909
     * utilisé pour créer cette requête.
910
     *
911
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
912
     */
913
    public function getServerParams(): array
914
    {
915 4
        return $this->_environment;
916
    }
917
918
    /**
919
     * Obtenez tous les paramètres de requête conformément aux spécifications PSR-7. Pour lire des valeurs de requête spécifiques
920
     * utilisez la méthode alternative getQuery().
921
     *
922
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
923
     */
924
    public function getQueryParams(): array
925
    {
926 2
        return $this->query;
927
    }
928
929
    /**
930
     * Mettez à jour les données de la chaîne de requête et obtenez une nouvelle instance.
931
     *
932
     * @param array $query Les données de la chaîne de requête à utiliser
933
     *
934
     * @see http://www.php-fig.org/psr/psr-7/ Cette méthode fait partie de l'interface de requête du serveur PSR-7.
935
     */
936
    public function withQueryParams(array $query): static
937
    {
938
        $new        = clone $this;
939
        $new->query = $query;
940
941
        return $new;
942
    }
943
944
    /**
945
     * Obtenez l'hôte sur lequel la demande a été traitée.
946
     */
947
    public function host(): ?string
948
    {
949
        if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_HOST')) {
950
            return $this->getEnv('HTTP_X_FORWARDED_HOST');
951
        }
952
953
        return $this->getEnv('HTTP_HOST');
954
    }
955
956
    /**
957
     * Obtenez le port sur lequel la demande a été traitée.
958
     */
959
    public function port(): ?string
960
    {
961
        if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_PORT')) {
962
            return $this->getEnv('HTTP_X_FORWARDED_PORT');
963
        }
964
965
        return $this->getEnv('SERVER_PORT');
966
    }
967
968
    /**
969
     * Obtenez le schéma d'URL actuel utilisé pour la demande.
970
     *
971
     * par exemple. 'http' ou 'https'
972
     */
973
    public function scheme(): ?string
974
    {
975
        if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_PROTO')) {
976 2
            return $this->getEnv('HTTP_X_FORWARDED_PROTO');
977
        }
978
979 8
        return $this->getEnv('HTTPS') ? 'https' : 'http';
980
    }
981
982
    /**
983
     * Obtenez le nom de domaine et incluez les segments $tldLength du tld.
984
     *
985
     * @param int $tldLength Nombre de segments que contient votre tld. Par exemple : `example.com` contient 1 tld.
986
     *                       Alors que `example.co.uk` contient 2.
987
     *
988
     * @return string Nom de domaine sans sous-domaines.
989
     */
990
    public function domain(int $tldLength = 1): string
991
    {
992
        $host = $this->host();
993
        if (empty($host)) {
994
            return '';
995
        }
996
997
        $segments = explode('.', $host);
998
        $domain   = array_slice($segments, -1 * ($tldLength + 1));
999
1000
        return implode('.', $domain);
1001
    }
1002
1003
    /**
1004
     * Obtenez les sous-domaines d'un hôte.
1005
     *
1006
     * @param int $tldLength Nombre de segments que contient votre tld. Par exemple : `example.com` contient 1 tld.
1007
     *                       Alors que `example.co.uk` contient 2.
1008
     *
1009
     * @return string[] Un tableau de sous-domaines.
1010
     */
1011
    public function subdomains(int $tldLength = 1): array
1012
    {
1013
        $host = $this->host();
1014
        if (empty($host)) {
1015
            return [];
1016
        }
1017
1018
        $segments = explode('.', $host);
1019
1020
        return array_slice($segments, 0, -1 * ($tldLength + 1));
1021
    }
1022
1023
    /**
1024
     * Obtient une liste de types de contenu acceptables par le navigateur client dans l'ordre préférable.
1025
     *
1026
     * @return string[]
1027
     */
1028
    public function getAcceptableContentTypes(): array
1029
    {
1030 2
        $raw    = $this->parseAccept();
1031 2
        $accept = [];
1032
1033
        foreach ($raw as $types) {
1034 2
            $accept = array_merge($accept, $types);
1035
        }
1036
1037 2
        return $accept;
1038
    }
1039
1040
    /**
1041
     * Découvrez quels types de contenu le client accepte ou vérifiez s'il accepte un
1042
     * type particulier de contenu.
1043
     *
1044
     * #### Obtenir tous les types :
1045
     *
1046
     * ```
1047
     * $this->request->accepts();
1048
     * ```
1049
     *
1050
     * #### Vérifier un seul type :
1051
     *
1052
     * ```
1053
     * $this->request->accepts('application/json');
1054
     * ```
1055
     *
1056
     * Cette méthode ordonnera les types de contenu renvoyés par les valeurs de préférence indiquées
1057
     * par le client.
1058
     *
1059
     * @param array|string|null $types Le type de contenu à vérifier. Laissez null pour obtenir tous les types qu'un client accepte.
1060
     *
1061
     * @return bool|string[] Soit un tableau de tous les types acceptés par le client, soit un booléen s'il accepte le type fourni.
1062
     */
1063
    public function accepts(null|array|string $types = null)
1064
    {
1065
        $accept = $this->getAcceptableContentTypes();
1066
1067
        if ($types === null) {
1068
            return $accept;
1069
        }
1070
1071
        foreach ((array) $types as $type) {
1072
            if (in_array($type, $accept, true)) {
1073
                return true;
1074
            }
1075
        }
1076
1077
        return false;
1078
    }
1079
1080
    /**
1081
     * Analyser l'en-tête HTTP_ACCEPT et renvoyer un tableau trié avec les types de contenu
1082
     * comme clés et valeurs pref comme valeurs.
1083
     *
1084
     * Généralement, vous souhaitez utiliser {@link \BlitzPHP\Http\ServerRequest::accepts()} pour obtenir une liste simple
1085
     * des types de contenu acceptés.
1086
     *
1087
     * @return array Un tableau de `prefValue => [contenu/types]`
1088
     */
1089
    public function parseAccept(): array
1090
    {
1091 2
        return $this->_parseAcceptWithQualifier($this->getHeaderLine('Accept'));
1092
    }
1093
1094
    /**
1095
     * Obtenez les langues acceptées par le client ou vérifiez si une langue spécifique est acceptée.
1096
     *
1097
     * Obtenez la liste des langues acceptées :
1098
     *
1099
     * ``` \BlitzPHP\Http\ServerRequest::acceptLanguage(); ```
1100
     *
1101
     * Vérifiez si une langue spécifique est acceptée :
1102
     *
1103
     * ``` \BlitzPHP\Http\ServerRequest::acceptLanguage('es-es'); ```
1104
     *
1105
     * @return array|bool Si un $language est fourni, un booléen. Sinon, le tableau des langues acceptées.
1106
     */
1107
    public function acceptLanguage(?string $language = null)
1108
    {
1109
        $raw    = $this->_parseAcceptWithQualifier($this->getHeaderLine('Accept-Language'));
1110
        $accept = [];
1111
1112
        foreach ($raw as $languages) {
1113
            foreach ($languages as &$lang) {
1114
                if (strpos($lang, '_')) {
1115
                    $lang = str_replace('_', '-', $lang);
1116
                }
1117
                $lang = strtolower($lang);
1118
            }
1119
            $accept = array_merge($accept, $languages);
1120
        }
1121
        if ($language === null) {
1122
            return $accept;
1123
        }
1124
1125
        return in_array(strtolower($language), $accept, true);
1126
    }
1127
1128
    /**
1129
     * Analysez les en-têtes Accept* avec les options de qualificateur.
1130
     *
1131
     * Seuls les qualificatifs seront extraits, toutes les autres extensions acceptées seront
1132
     * jetés car ils ne sont pas fréquemment utilisés.
1133
     */
1134
    protected function _parseAcceptWithQualifier(string $header): array
1135
    {
1136 2
        $accept  = [];
1137 2
        $headers = explode(',', $header);
1138
1139
        foreach (array_filter($headers) as $value) {
1140 2
            $prefValue = '1.0';
1141
            $value     = trim($value);
1142
1143
            $semiPos = strpos($value, ';');
1144
            if ($semiPos !== false) {
1145
                $params = explode(';', $value);
1146
                $value  = trim($params[0]);
1147
1148
                foreach ($params as $param) {
1149
                    $qPos = strpos($param, 'q=');
1150
                    if ($qPos !== false) {
1151
                        $prefValue = substr($param, $qPos + 2);
1152
                    }
1153
                }
1154
            }
1155
1156
            if (! isset($accept[$prefValue])) {
1157
                $accept[$prefValue] = [];
1158
            }
1159
            if ($prefValue) {
1160
                $accept[$prefValue][] = $value;
1161
            }
1162
        }
1163 2
        krsort($accept);
1164
1165 2
        return $accept;
1166
    }
1167
1168
    /**
1169
     * Lire une valeur de requête spécifique ou un chemin en pointillés.
1170
     *
1171
     * Les développeurs sont encouragés à utiliser getQueryParams() s'ils ont besoin de tout le tableau de requête,
1172
     * car il est compatible PSR-7, et cette méthode ne l'est pas. En utilisant Hash::get(), vous pouvez également obtenir des paramètres uniques.
1173
     *
1174
     * ### Alternative PSR-7
1175
     *
1176
     * ```
1177
     * $value = Arr::get($request->getQueryParams(), 'Post.id');
1178
     * ```
1179
     *
1180
     * @param string|null $name    Le nom ou le chemin en pointillé vers le paramètre de requête ou null pour tout lire.
1181
     * @param mixed       $default La valeur par défaut si le paramètre nommé n'est pas défini et que $name n'est pas nul.
1182
     *
1183
     * @return array|string|null Requête de données.
1184
     *
1185
     * @see ServerRequest::getQueryParams()
1186
     */
1187
    public function getQuery(?string $name = null, $default = null)
1188
    {
1189
        if ($name === null) {
1190
            return $this->query;
1191
        }
1192
1193 6
        return Arr::get($this->query, $name, $default);
1194
    }
1195
1196
    /**
1197
     * Fournit un accesseur sécurisé pour les données de requête. Permet
1198
     * vous permet d'utiliser des chemins compatibles Arr::get().
1199
     *
1200
     * ### Lecture des valeurs.
1201
     *
1202
     * ```
1203
     * // récupère toutes les données
1204
     * $request->getData();
1205
     *
1206
     * // Lire un champ spécifique.
1207
     * $request->getData('Post.title');
1208
     *
1209
     * // Avec une valeur par défaut.
1210
     * $request->getData('Post.not there', 'default value');
1211
     * ```
1212
     *
1213
     * Lors de la lecture des valeurs, vous obtiendrez `null` pour les clés/valeurs qui n'existent pas.
1214
     *
1215
     * Les développeurs sont encouragés à utiliser getParsedBody() s'ils ont besoin de tout le tableau de données,
1216
     * car il est compatible PSR-7, et cette méthode ne l'est pas. En utilisant Hash::get(), vous pouvez également obtenir des paramètres uniques.
1217
     *
1218
     * ### Alternative PSR-7
1219
     *
1220
     * ```
1221
     * $value = Arr::get($request->getParsedBody(), 'Post.id');
1222
     * ```
1223
     *
1224
     * @param string|null $name    Nom séparé par un point de la valeur à lire. Ou null pour lire toutes les données.
1225
     * @param mixed       $default Les données par défaut.
1226
     *
1227
     * @return mixed La valeur en cours de lecture.
1228
     */
1229
    public function getData(?string $name = null, $default = null)
1230
    {
1231
        if ($name === null) {
1232 10
            return $this->data;
1233
        }
1234
        if (! is_array($this->data)) {
1235
            return $default;
1236
        }
1237
1238 4
        return Arr::get($this->data, $name, $default);
1239
    }
1240
1241
    /**
1242
     * Lire les données de cookie à partir des données de cookie de la demande.
1243
     *
1244
     * @param string            $key     La clé ou le chemin en pointillés que vous voulez lire.
1245
     * @param array|string|null $default La valeur par défaut si le cookie n'est pas défini.
1246
     *
1247
     * @return array|string|null Soit la valeur du cookie, soit null si la valeur n'existe pas.
1248
     */
1249
    public function getCookie(string $key, $default = null)
1250
    {
1251 2
        return Arr::get($this->cookies, $key, $default);
1252
    }
1253
1254
    /**
1255
     * Obtenir une collection de cookies basée sur les cookies de la requête
1256
     *
1257
     * La CookieCollection vous permet d'interagir avec les cookies de demande en utilisant
1258
     * Objets `\BlitzPHP\Http\Cookie\Cookie` et peut faire des cookies de demande de conversion
1259
     * dans les cookies de réponse plus facile.
1260
     *
1261
     * Cette méthode créera une nouvelle collection de cookies à chaque appel.
1262
     * Il s'agit d'une optimisation qui permet d'allouer moins d'objets jusqu'à
1263
     * plus la CookieCollection est nécessaire. En général, vous devriez préférer
1264
     * `getCookie()` et `getCookieParams()` sur cette méthode. Utilisation d'une collection de cookies
1265
     * est idéal si vos cookies contiennent des données complexes encodées en JSON.
1266
     */
1267
    public function getCookieCollection(): CookieCollection
1268
    {
1269
        return CookieCollection::createFromServerRequest($this);
1270
    }
1271
1272
    /**
1273
     * Remplacez les cookies de la requête par ceux contenus dans
1274
     * la CookieCollection fournie.
1275
     */
1276
    public function withCookieCollection(CookieCollection $cookies): static
1277
    {
1278
        $new    = clone $this;
1279
        $values = [];
1280
1281
        foreach ($cookies as $cookie) {
1282
            $values[$cookie->getName()] = $cookie->getValue();
1283
        }
1284
        $new->cookies = $values;
1285
1286
        return $new;
1287
    }
1288
1289
    /**
1290
     * Obtenez toutes les données de cookie de la requête.
1291
     *
1292
     * @return array Un tableau de données de cookie.
1293
     */
1294
    public function getCookieParams(): array
1295
    {
1296
        return $this->cookies;
1297
    }
1298
1299
    /**
1300
     * Remplacez les cookies et obtenez une nouvelle instance de requête.
1301
     *
1302
     * @param array $cookies Les nouvelles données de cookie à utiliser.
1303
     */
1304
    public function withCookieParams(array $cookies): static
1305
    {
1306
        $new          = clone $this;
1307
        $new->cookies = $cookies;
1308
1309
        return $new;
1310
    }
1311
1312
    /**
1313
     * Obtenez les données de corps de requête analysées.
1314
     *
1315
     * Si la requête Content-Type est soit application/x-www-form-urlencoded
1316
     * ou multipart/form-data, et la méthode de requête est POST, ce sera le
1317
     * publier des données. Pour les autres types de contenu, il peut s'agir de la requête désérialisée
1318
     * corps.
1319
     *
1320
     * @return array|object|null Les paramètres de corps désérialisés, le cas échéant.
1321
     *                           Il s'agira généralement d'un tableau.
1322
     */
1323
    public function getParsedBody()
1324
    {
1325 8
        return $this->data;
1326
    }
1327
1328
    /**
1329
     * Mettez à jour le corps analysé et obtenez une nouvelle instance.
1330
     *
1331
     * @param array|object|null $data Les données de corps désérialisées. Cette volonté
1332
     *                                être généralement dans un tableau ou un objet.
1333
     */
1334
    public function withParsedBody($data): static
1335
    {
1336 8
        $new       = clone $this;
1337 8
        $new->data = $data;
1338
1339 8
        return $new;
1340
    }
1341
1342
    /**
1343
     * Récupère la version du protocole HTTP sous forme de chaîne.
1344
     *
1345
     * @return string Version du protocole HTTP.
1346
     */
1347
    public function getProtocolVersion(): string
1348
    {
1349
        if ($this->protocol) {
1350
            return $this->protocol;
1351
        }
1352
1353
        // Remplissez paresseusement ces données car elles ne sont généralement pas utilisées.
1354
        preg_match('/^HTTP\/([\d.]+)$/', (string) $this->getEnv('SERVER_PROTOCOL'), $match);
1355
        $protocol = '1.1';
1356
        if (isset($match[1])) {
1357
            $protocol = $match[1];
1358
        }
1359
        $this->protocol = $protocol;
1360
1361
        return $this->protocol;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->protocol could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
1362
    }
1363
1364
    /**
1365
     * Renvoie une instance avec la version de protocole HTTP spécifiée.
1366
     *
1367
     * La chaîne de version DOIT contenir uniquement le numéro de version HTTP (par exemple,
1368
     * "1.1", "1.0").
1369
     *
1370
     * @param string $version Version du protocole HTTP
1371
     */
1372
    public function withProtocolVersion(string $version): static
1373
    {
1374
        if (! preg_match('/^(1\.[01]|2(\.[0])?)$/', $version)) {
1375
            throw new InvalidArgumentException(sprintf('Version de protocole `%s` non prise en charge fournie.', $version));
1376
        }
1377
        $new           = clone $this;
1378
        $new->protocol = $version;
1379
1380
        return $new;
1381
    }
1382
1383
    /**
1384
     * Obtenez une valeur à partir des données d'environnement de la demande.
1385
     * Se replier sur env() si la clé n'est pas définie dans la propriété $environment.
1386
     *
1387
     * @param string      $key     La clé à partir de laquelle vous voulez lire.
1388
     * @param string|null $default Valeur par défaut lors de la tentative de récupération d'un environnement
1389
     *                             valeur de la variable qui n'existe pas.
1390
     *
1391
     * @return string|null Soit la valeur de l'environnement, soit null si la valeur n'existe pas.
1392
     */
1393
    public function getEnv(string $key, ?string $default = null): ?string
1394
    {
1395 28
        $key = strtoupper($key);
1396
        if (! array_key_exists($key, $this->_environment) || null === $this->_environment[$key]) {
1397 18
            $this->_environment[$key] = env($key, $default);
1398
        }
1399
1400 28
        return $this->_environment[$key];
1401
    }
1402
1403
    /**
1404
     * Mettez à jour la demande avec un nouvel élément de données d'environnement.
1405
     *
1406
     * Renvoie un objet de requête mis à jour. Cette méthode retourne
1407
     * un *nouvel* objet de requête et ne mute pas la requête sur place.
1408
     */
1409
    public function withEnv(string $key, string $value): static
1410
    {
1411 10
        $new                     = clone $this;
1412 10
        $new->_environment[$key] = $value;
1413 10
        $new->clearDetectorCache();
1414
1415 10
        return $new;
1416
    }
1417
1418
    /**
1419
     * Autoriser uniquement certaines méthodes de requête HTTP, si la méthode de requête ne correspond pas
1420
     * une erreur 405 s'affichera et l'en-tête de réponse "Autoriser" requis sera défini.
1421
     *
1422
     * Exemple:
1423
     *
1424
     * $this->request->allowMethod('post');
1425
     * ou alors
1426
     * $this->request->allowMethod(['post', 'delete']);
1427
     *
1428
     * Si la requête est GET, l'en-tête de réponse "Autoriser : POST, SUPPRIMER" sera défini
1429
     * et une erreur 405 sera renvoyée.
1430
     *
1431
     * @param string|string[] $methods Méthodes de requête HTTP autorisées.
1432
     *
1433
     * @throws HttpException
1434
     */
1435
    public function allowMethod($methods): bool
1436
    {
1437
        $methods = (array) $methods;
1438
1439
        foreach ($methods as $method) {
1440
            if ($this->is($method)) {
1441
                return true;
1442
            }
1443
        }
1444
        $allowed = strtoupper(implode(', ', $methods));
1445
1446
        throw HttpException::methodNotAllowed($allowed);
1447
    }
1448
1449
    /**
1450
     * Mettez à jour la demande avec un nouvel élément de données de demande.
1451
     *
1452
     * Renvoie un objet de requête mis à jour. Cette méthode retourne
1453
     * un *nouvel* objet de requête et ne mute pas la requête sur place.
1454
     *
1455
     * Utilisez `withParsedBody()` si vous devez remplacer toutes les données de la requête.
1456
     *
1457
     * @param string $name Le chemin séparé par des points où insérer $value.
1458
     */
1459
    public function withData(string $name, mixed $value): static
1460
    {
1461
        $copy = clone $this;
1462
1463
        if (is_array($copy->data)) {
1464
            $copy->data = Arr::insert($copy->data, $name, $value);
1465
        }
1466
1467
        return $copy;
1468
    }
1469
1470
    /**
1471
     * Mettre à jour la demande en supprimant un élément de données.
1472
     *
1473
     * Renvoie un objet de requête mis à jour. Cette méthode retourne
1474
     * un *nouvel* objet de requête et ne mute pas la requête sur place.
1475
     *
1476
     * @param string $name Le chemin séparé par des points à supprimer.
1477
     */
1478
    public function withoutData(string $name): static
1479
    {
1480
        $copy = clone $this;
1481
1482
        if (is_array($copy->data)) {
1483
            $copy->data = Arr::remove($copy->data, $name);
1484
        }
1485
1486
        return $copy;
1487
    }
1488
1489
    /**
1490
     * Mettre à jour la requête avec un nouveau paramètre de routage
1491
     *
1492
     * Renvoie un objet de requête mis à jour. Cette méthode retourne
1493
     * un *nouvel* objet de requête et ne mute pas la requête sur place.
1494
     *
1495
     * @param string $name Le chemin séparé par des points où insérer $value.
1496
     */
1497
    public function withParam(string $name, mixed $value): static
1498
    {
1499 2
        $copy         = clone $this;
1500 2
        $copy->params = Arr::insert($copy->params, $name, $value);
1501
1502 2
        return $copy;
1503
    }
1504
1505
    /**
1506
     * Accédez en toute sécurité aux valeurs dans $this->params.
1507
     */
1508
    public function getParam(string $name, mixed $default = null)
1509
    {
1510 2
        return Arr::get($this->params, $name, $default);
1511
    }
1512
1513
    /**
1514
     * Renvoie une instance avec l'attribut de requête spécifié.
1515
     *
1516
     * @param string $name  Le nom de l'attribut.
1517
     * @param mixed  $value La valeur de l'attribut.
1518
     */
1519
    public function withAttribute(string $name, mixed $value): static
1520
    {
1521
        $new = clone $this;
1522
        if (in_array($name, $this->emulatedAttributes, true)) {
1523
            $new->{$name} = $value;
1524
        } else {
1525
            $new->attributes[$name] = $value;
1526
        }
1527
1528
        return $new;
1529
    }
1530
1531
    /**
1532
     * Renvoie une instance sans l'attribut de requête spécifié.
1533
     *
1534
     * @param string $name Le nom de l'attribut.
1535
     *
1536
     * @throws InvalidArgumentException
1537
     */
1538
    public function withoutAttribute(string $name): static
1539
    {
1540
        $new = clone $this;
1541
        if (in_array($name, $this->emulatedAttributes, true)) {
1542
            throw new InvalidArgumentException(
1543
                "Vous ne pouvez pas supprimer '{$name}'. C'est un attribut BlitzPHP obligatoire."
1544
            );
1545
        }
1546
        unset($new->attributes[$name]);
1547
1548
        return $new;
1549
    }
1550
1551
    /**
1552
     * Tentatives d'obtenir de vieilles données d'entrée qui a été flashé à la session avec redirect_with_input().
1553
     * Il vérifie d'abord les données dans les anciennes données POST, puis les anciennes données GET et enfin vérifier les tableaux de points
1554
     *
1555
     * @return array|string|null
1556
     */
1557
    public function getOldInput(string $key)
1558
    {
1559
        return $this->session()->getOldInput($key);
1560
    }
1561
1562
    /**
1563
     * Lire un attribut de la requête ou obtenir la valeur par défaut
1564
     *
1565
     * @param string $name    Le nom de l'attribut.
1566
     * @param mixed  $default La valeur par défaut si l'attribut n'a pas été défini.
1567
     */
1568
    public function getAttribute(string $name, mixed $default = null): mixed
1569
    {
1570
        if (in_array($name, $this->emulatedAttributes, true)) {
1571
            if ($name === 'here') {
1572
                return $this->base . $this->uri->getPath();
1573
            }
1574
1575
            return $this->{$name};
1576
        }
1577
        if (array_key_exists($name, $this->attributes)) {
1578 6
            return $this->attributes[$name];
1579
        }
1580
1581 6
        return $default;
1582
    }
1583
1584
    /**
1585
     * Obtenez tous les attributs de la requête.
1586
     *
1587
     * Cela inclura les attributs params, webroot, base et here fournis par BlitzPHP.
1588
     */
1589
    public function getAttributes(): array
1590
    {
1591
        $emulated = [
1592
            'params'  => $this->params,
1593
            'webroot' => $this->webroot,
1594
            'base'    => $this->base,
1595
            'here'    => $this->base . $this->uri->getPath(),
1596
        ];
1597
1598
        return $this->attributes + $emulated;
1599
    }
1600
1601
    /**
1602
     * Obtenez le fichier téléchargé à partir d'un chemin en pointillés.
1603
     *
1604
     * @param string $path Le chemin séparé par des points vers le fichier que vous voulez.
1605
     *
1606
     * @return UploadedFileInterface|UploadedFileInterface[]|null
1607
     */
1608
    public function getUploadedFile(string $path)
1609
    {
1610 2
        $file = Arr::get($this->uploadedFiles, $path);
1611
        if (is_array($file)) {
1612
            foreach ($file as $f) {
1613
                if (! ($f instanceof UploadedFile)) {
1614 2
                    return null;
1615
                }
1616
            }
1617
1618 2
            return $file;
1619
        }
1620
1621
        if (! ($file instanceof UploadedFileInterface)) {
1622 2
            return null;
1623
        }
1624
1625 2
        return $file;
1626
    }
1627
1628
    /**
1629
     * Obtenez le tableau des fichiers téléchargés à partir de la requête.
1630
     */
1631
    public function getUploadedFiles(): array
1632
    {
1633 6
        return $this->uploadedFiles;
1634
    }
1635
1636
    /**
1637
     * Mettez à jour la demande en remplaçant les fichiers et en créant une nouvelle instance.
1638
     *
1639
     * @param array $uploadedFiles Un tableau d'objets de fichiers téléchargés.
1640
     *
1641
     * @throws InvalidArgumentException lorsque $files contient un objet invalide.
1642
     */
1643
    public function withUploadedFiles(array $uploadedFiles): static
1644
    {
1645 10
        $this->validateUploadedFiles($uploadedFiles, '');
1646 10
        $new                = clone $this;
1647 10
        $new->uploadedFiles = $uploadedFiles;
1648
1649 10
        return $new;
1650
    }
1651
1652
    /**
1653
     * Validez de manière récursive les données de fichier téléchargées.
1654
     *
1655
     * @param array  $uploadedFiles Le nouveau tableau de fichiers à valider.
1656
     * @param string $path          Le chemin jusqu'ici.
1657
     *
1658
     * @throws InvalidArgumentException Si des éléments feuilles ne sont pas des fichiers valides.
1659
     */
1660
    protected function validateUploadedFiles(array $uploadedFiles, string $path): void
1661
    {
1662
        foreach ($uploadedFiles as $key => $file) {
1663
            if (is_array($file)) {
1664 4
                $this->validateUploadedFiles($file, $key . '.');
1665
1666 4
                continue;
1667
            }
1668
1669
            if (! $file instanceof UploadedFileInterface) {
1670 2
                throw new InvalidArgumentException("Fichier invalide à `{$path}{$key}`");
1671
            }
1672
        }
1673
    }
1674
1675
    /**
1676
     * Obtient le corps du message.
1677
     */
1678
    public function getBody(): StreamInterface
1679
    {
1680 2
        return $this->stream;
1681
    }
1682
1683
    /**
1684
     * Renvoie une instance avec le corps de message spécifié.
1685
     */
1686
    public function withBody(StreamInterface $body): static
1687
    {
1688
        $new         = clone $this;
1689
        $new->stream = $body;
1690
1691
        return $new;
1692
    }
1693
1694
    /**
1695
     * Récupère l'instance d'URI.
1696
     */
1697
    public function getUri(): UriInterface
1698
    {
1699 14
        return $this->uri;
1700
    }
1701
1702
    /**
1703
     * Renvoie une instance avec l'uri spécifié
1704
     *
1705
     * *Attention* Remplacer l'Uri ne mettra pas à jour la `base`, `webroot`,
1706
     * et les attributs `url`.
1707
     *
1708
     * @param bool $preserveHost Indique si l'hôte doit être conservé.
1709
     */
1710
    public function withUri(UriInterface $uri, bool $preserveHost = false): static
1711
    {
1712 8
        $new      = clone $this;
1713 8
        $new->uri = $uri;
1714
1715
        if ($preserveHost && $this->hasHeader('Host')) {
1716 2
            return $new;
1717
        }
1718
1719 8
        $host = $uri->getHost();
1720
        if (! $host) {
1721 4
            return $new;
1722
        }
1723 6
        $port = $uri->getPort();
1724
        if ($port) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $port of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1725 6
            $host .= ':' . $port;
1726
        }
1727 6
        $new->_environment['HTTP_HOST'] = $host;
1728
1729 6
        return $new;
1730
    }
1731
1732
    /**
1733
     * Créez une nouvelle instance avec une cible de demande spécifique.
1734
     *
1735
     * Vous pouvez utiliser cette méthode pour écraser la cible de la demande qui est
1736
     * déduit de l'Uri de la requête. Cela vous permet également de modifier la demande
1737
     * la forme de la cible en une forme absolue, une forme d'autorité ou une forme d'astérisque
1738
     *
1739
     * @see https://tools.ietf.org/html/rfc7230#section-2.7 (pour les différentes formes de demande-cible autorisées dans les messages de demande)
1740
     *
1741
     * @param string $requestTarget La cible de la requête.
1742
     *
1743
     * @psalm-suppress MoreSpecificImplementedParamType
1744
     */
1745
    public function withRequestTarget(string $requestTarget): static
1746
    {
1747 2
        $new                = clone $this;
1748 2
        $new->requestTarget = $requestTarget;
1749
1750 2
        return $new;
1751
    }
1752
1753
    /**
1754
     * Récupère la cible de la requête.
1755
     *
1756
     * Récupère la cible de la demande du message soit telle qu'elle a été demandée,
1757
     * ou comme défini avec `withRequestTarget()`. Par défaut, cela renverra le
1758
     * chemin relatif de l'application sans répertoire de base et la chaîne de requête
1759
     * défini dans l'environnement SERVER.
1760
     */
1761
    public function getRequestTarget(): string
1762
    {
1763
        if ($this->requestTarget !== null) {
1764
            return $this->requestTarget;
1765
        }
1766
1767 4
        $target = $this->uri->getPath();
1768
        if ($this->uri->getQuery()) {
1769 4
            $target .= '?' . $this->uri->getQuery();
1770
        }
1771
1772
        if (empty($target)) {
1773 4
            $target = '/';
1774
        }
1775
1776 4
        return $target;
1777
    }
1778
1779
    /**
1780
     * Récupère le chemin de la requête en cours.
1781
     */
1782
    public function getPath(): string
1783
    {
1784
        if ($this->requestTarget === null) {
1785 4
            return $this->uri->getPath();
1786
        }
1787
1788 2
        [$path] = explode('?', $this->requestTarget);
1789
1790 2
        return $path;
1791
    }
1792
1793
    /**
1794
     * Fournit un moyen pratique de travailler avec la classe Negotiate
1795
     * pour la négociation de contenu.
1796
     */
1797
    public function negotiate(string $type, array $supported, bool $strictMatch = false): string
1798
    {
1799
        if (null === $this->negotiator) {
1800
            $this->negotiator = Services::negotiator($this, true);
1801
        }
1802
1803
        switch (strtolower($type)) {
1804
            case 'media':
1805
                return $this->negotiator->media($supported, $strictMatch);
1806
1807
            case 'charset':
1808
                return $this->negotiator->charset($supported);
1809
1810
            case 'encoding':
1811
                return $this->negotiator->encoding($supported);
1812
1813
            case 'language':
1814
                return $this->negotiator->language($supported);
1815
        }
1816
1817
        throw new HttpException($type . ' is not a valid negotiation type. Must be one of: media, charset, encoding, language.');
1818
    }
1819
1820
    /**
1821
     * Définit la chaîne locale pour cette requête.
1822
     */
1823
    public function withLocale(string $locale): static
1824
    {
1825
        $validLocales = config('app.supported_locales');
1826
        // S'il ne s'agit pas d'un paramètre régional valide, définissez-le
1827
        // aux paramètres régionaux par défaut du site.
1828
        if (! in_array($locale, $validLocales, true)) {
0 ignored issues
show
Bug introduced by
$validLocales of type BlitzPHP\Config\Config|null is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1828
        if (! in_array($locale, /** @scrutinizer ignore-type */ $validLocales, true)) {
Loading history...
1829
            $locale = config('app.language');
1830
        }
1831
1832
        Services::translator()->setLocale($locale);
0 ignored issues
show
Bug introduced by
It seems like $locale can also be of type BlitzPHP\Config\Config; however, parameter $locale of BlitzPHP\Translator\Translate::setLocale() does only seem to accept null|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

1832
        Services::translator()->setLocale(/** @scrutinizer ignore-type */ $locale);
Loading history...
1833
1834
        return $this->withAttribute('locale', $locale);
1835
    }
1836
1837
    /**
1838
     * Obtient les paramètres régionaux actuels, avec un retour à la valeur par défaut
1839
     * locale si aucune n'est définie.
1840
     */
1841
    public function getLocale(): string
1842
    {
1843 6
        $locale = $this->getAttribute('locale');
1844
        if (empty($locale)) {
1845 6
            $locale = $this->getAttribute('lang');
1846
        }
1847
1848 6
        return $locale ?? Services::translator()->getLocale();
1849
    }
1850
}
1851