ServerRequest::domain()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 10
ccs 0
cts 4
cp 0
crap 6
rs 10
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\Exceptions\FrameworkException;
16
use BlitzPHP\Exceptions\HttpException;
17
use BlitzPHP\Filesystem\Files\UploadedFile;
18
use BlitzPHP\Session\Cookie\CookieCollection;
19
use BlitzPHP\Session\Store;
20
use BlitzPHP\Utilities\Iterable\Arr;
21
use Closure;
22
use GuzzleHttp\Psr7\ServerRequest as Psr7ServerRequest;
23
use GuzzleHttp\Psr7\Stream;
24
use GuzzleHttp\Psr7\Utils;
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 array|object|null $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 list<string>
0 ignored issues
show
Bug introduced by
The type BlitzPHP\Http\list was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
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<string, 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 list<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 31
        ];
212
213 31
        $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'] = single_service('session');
225
        }
226
227
        if (empty($config['environment']['REQUEST_METHOD'])) {
228 29
            $config['environment']['REQUEST_METHOD'] = $_SERVER['REQUEST_METHOD'] ?? 'GET';
229
        }
230
231 31
        $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
        } elseif ($config['url'] !== '') {
239 2
            $config = $this->processUrlOption($config);
240 2
            $uri    = new Uri(implode('?', [$config['url'], $config['environment']['QUERY_STRING'] ?? '']));
241
        } else {
242 27
            $uri = new Uri(Psr7ServerRequest::getUriFromGlobals()->__toString());
243
        }
244
245
        if (in_array($uri->getHost(), ['localhost', '127.0.0.1'], true)) {
246 27
            $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 object; 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

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

286
            $config['url'] = '/' . /** @scrutinizer ignore-type */ $config['url'];
Loading history...
287
        }
288
289
        if (str_contains($config['url'], '?')) {
290 2
            [$config['url'], $config['environment']['QUERY_STRING']] = explode('?', $config['url']);
291
292 2
            parse_str($config['environment']['QUERY_STRING'], $queryArgs);
293 2
            $config['query'] += $queryArgs;
294
        }
295
296 2
        $config['environment']['REQUEST_URI'] = $config['url'];
297
298 2
        return $config;
299
    }
300
301
    /**
302
     * Obtenez le type de contenu utilisé dans cette requête.
303
     */
304
    public function contentType(): ?string
305
    {
306 2
        return $this->getEnv('CONTENT_TYPE') ?: $this->getEnv('HTTP_CONTENT_TYPE');
307
    }
308
309
    /**
310
     * Renvoie l'instance de l'objet Session pour cette requête
311
     */
312
    public function session(): Store
313
    {
314 10
        return $this->session;
315
    }
316
317
    /**
318
     * Obtenez l'adresse IP que le client utilise ou dit qu'il utilise.
319
     */
320
    public function clientIp(): string
321
    {
322
        if ($this->trustProxy && $this->getEnv('HTTP_X_FORWARDED_FOR')) {
323
            $addresses = array_map('trim', explode(',', $this->getEnv('HTTP_X_FORWARDED_FOR')));
0 ignored issues
show
Bug introduced by
It seems like $this->getEnv('HTTP_X_FORWARDED_FOR') can also be of type null; however, parameter $string of explode() 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

323
            $addresses = array_map('trim', explode(',', /** @scrutinizer ignore-type */ $this->getEnv('HTTP_X_FORWARDED_FOR')));
Loading history...
324
            $trusted   = $this->trustedProxies !== [];
325
            $n         = count($addresses);
326
327
            if ($trusted) {
328
                $trusted = array_diff($addresses, $this->trustedProxies);
329
                $trusted = (count($trusted) === 1);
330
            }
331
332
            if ($trusted) {
333
                return $addresses[0];
334
            }
335
336
            return $addresses[$n - 1];
337
        }
338
339
        if ($this->trustProxy && $this->getEnv('HTTP_X_REAL_IP')) {
340
            $ipaddr = $this->getEnv('HTTP_X_REAL_IP');
341
        } elseif ($this->trustProxy && $this->getEnv('HTTP_CLIENT_IP')) {
342
            $ipaddr = $this->getEnv('HTTP_CLIENT_IP');
343
        } else {
344
            $ipaddr = $this->getEnv('REMOTE_ADDR');
345
        }
346
347
        return trim((string) $ipaddr);
348
    }
349
350
    /**
351
     * Enregistrer des proxys de confiance
352
     *
353
     * @param list<string> $proxies ips liste des proxys de confiance
354
     */
355
    public function setTrustedProxies(array $proxies): void
356
    {
357 2
        $this->trustedProxies = $proxies;
0 ignored issues
show
Documentation Bug introduced by
It seems like $proxies of type array is incompatible with the declared type BlitzPHP\Http\list of property $trustedProxies.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
358 2
        $this->trustProxy     = true;
359 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

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

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

1812
        if (! in_array($locale, /** @scrutinizer ignore-type */ $validLocales, true)) {
Loading history...
1813
            $locale = config('app.language');
1814
        }
1815
1816
        service('translator')->setLocale($locale);
1817
1818
        return $this->withAttribute('locale', $locale);
1819
    }
1820
1821
    /**
1822
     * Obtient les paramètres régionaux actuels, avec un retour à la valeur par défaut
1823
     * locale si aucune n'est définie.
1824
     */
1825
    public function getLocale(): string
1826
    {
1827
        if (empty($locale = $this->getAttribute('locale'))) {
1828 6
            $locale = $this->getAttribute('lang');
1829
        }
1830
1831 6
        return $locale ?? config('app.language');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $locale ?? config('app.language') could return the type null|object which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
1832
    }
1833
}
1834