Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.16% covered (success)
98.16%
160 / 163
95.59% covered (success)
95.59%
65 / 68
CRAP
0.00% covered (danger)
0.00%
0 / 1
Request
98.16% covered (success)
98.16%
160 / 163
95.59% covered (success)
95.59%
65 / 68
119
0.00% covered (danger)
0.00%
0 / 1
 __construct
96.43% covered (success)
96.43%
27 / 28
0.00% covered (danger)
0.00%
0 / 1
14
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createWithBasePath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAuth
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getAuth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAuth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAccept
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 accepts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPreferredType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 acceptsHtml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 acceptsJson
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 acceptsXml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withMethod
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 isGet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isHead
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isPost
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isPut
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isTrace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isOptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isConnect
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isPatch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSecure
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 getDocumentRoot
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMethod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPort
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getScheme
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getHost
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 getFullHost
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 getIp
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
6
 getCookie
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getServer
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getEnv
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getBasePath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUriString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFullUriString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSegment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSegments
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setBasePath
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 hasFiles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPost
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFiles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPut
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPatch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getQueryData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasQueryData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getParsedData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasParsedData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRawData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasRawData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getServerParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCookieParams
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withCookieParams
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getQueryParams
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 withQueryParams
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getUploadedFiles
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 withUploadedFiles
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getParsedBody
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 withParsedBody
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getAttributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAttribute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withAttribute
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 withoutAttribute
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
14
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Http\Server;
16
17use Pop\Http\Auth;
18use Pop\Http\Uri;
19use Pop\Http\AbstractRequest;
20use Pop\Http\Body;
21use Pop\Http\Server\AcceptHeader;
22use Pop\Http\Server\AcceptSpecificity;
23use Psr\Http\Message\ServerRequestInterface;
24
25/**
26 * HTTP server request class
27 *
28 * @category   Pop
29 * @package    Pop\Http
30 * @author     Nick Sagona, III <nick@popphp.org>
31 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
32 * @license    https://www.popphp.org/license     New BSD License
33 * @version    6.0.0
34 */
35class Request extends AbstractRequest implements ServerRequestInterface
36{
37
38    /**
39     * Server request data object
40     * @var ?Data
41     */
42    protected ?Data $data = null;
43
44    /**
45     * COOKIE array
46     * @var array
47     */
48    protected array $cookie = [];
49
50    /**
51     * SERVER array
52     * @var array
53     */
54    protected array $server = [];
55
56    /**
57     * ENV array
58     * @var array
59     */
60    protected array $env = [];
61
62    /**
63     * HTTP auth object
64     * @var ?Auth
65     */
66    protected ?Auth $auth = null;
67
68    /**
69     * Method override (from withMethod)
70     * @var ?string
71     */
72    protected ?string $methodOverride = null;
73
74    /**
75     * Cookie params override, per PSR-7 withCookieParams() (null = derive from $this->cookie)
76     * @var ?array
77     */
78    protected ?array $cookieParamsOverride = null;
79
80    /**
81     * Query params override, per PSR-7 withQueryParams() (null = derive from $this->data)
82     * @var ?array
83     */
84    protected ?array $queryParamsOverride = null;
85
86    /**
87     * Uploaded files override, per PSR-7 withUploadedFiles() (null = derive from $this->data)
88     * @var ?array
89     */
90    protected ?array $uploadedFilesOverride = null;
91
92    /**
93     * Parsed body override, per PSR-7 withParsedBody()
94     * @var mixed
95     */
96    protected mixed $parsedBodyOverride = null;
97
98    /**
99     * Whether withParsedBody() has been called (distinguishes "not set" from "set to null")
100     * @var bool
101     */
102    protected bool $parsedBodyOverridden = false;
103
104    /**
105     * Request attributes, per PSR-7 (middleware-style per-request key-value bag)
106     * @var array
107     */
108    protected array $attributes = [];
109
110    /**
111     * Constructor
112     *
113     * Instantiate the request object
114     *
115     * @param  Uri|string|null $uri
116     * @param  mixed           $filters
117     * @param  mixed           $streamToFile
118     * @param  bool            $populateFromGlobals
119     * @param  array           $serverParams
120     * @throws Exception|\Pop\Http\Exception
121     */
122    public function __construct(
123        Uri|string|null $uri = null, mixed $filters = null, mixed $streamToFile = null,
124        bool $populateFromGlobals = true, array $serverParams = []
125    )
126    {
127        parent::__construct($uri);
128
129        if ($populateFromGlobals) {
130            $this->cookie = array_key_exists('_COOKIE', $GLOBALS) ? $_COOKIE : [];
131            $this->server = array_key_exists('_SERVER', $GLOBALS) ? $_SERVER : [];
132            $this->env    = array_key_exists('_ENV', $GLOBALS)    ? $_ENV    : [];
133
134            // Get any possible request headers
135            if (function_exists('getallheaders')) {
136                $this->addHeaders(getallheaders());
137            } else {
138                foreach ($_SERVER as $key => $value) {
139                    if (str_starts_with($key, 'HTTP_')) {
140                        $key = ucfirst(strtolower(str_replace('HTTP_', '', $key)));
141                        if (str_contains($key, '_')) {
142                            $ary = explode('_', $key);
143                            foreach ($ary as $k => $v){
144                                $ary[$k] = ucfirst(strtolower($v));
145                            }
146                            $key = implode('-', $ary);
147                        }
148                        $this->addHeader($key, $value);
149                    }
150                }
151            }
152
153            if ($this->hasHeader('Authorization')) {
154                $this->setAuth(Auth::parse($this->getHeaderObject('Authorization')));
155            }
156        } else {
157            $this->server = $serverParams;
158        }
159
160        $contentType     = $this->getHeaderValue('Content-Type');
161        $contentEncoding = $this->getHeaderValue('Content-Encoding');
162
163        $this->data = new Data(
164            ($contentType !== null) ? (string)$contentType : null,
165            ($contentEncoding !== null) ? (string)$contentEncoding : null,
166            $filters, $streamToFile, $populateFromGlobals
167        );
168
169        if ($this->data->hasRawData()) {
170            $this->body = new Body($this->data->getRawData());
171        }
172    }
173
174    /**
175     * Factory to create a new request object
176     *
177     * @param  ?Uri  $uri
178     * @param  mixed $filters
179     * @param  mixed $streamToFile
180     * @throws Exception|\Pop\Http\Exception
181     * @return Request
182     */
183    public static function create(?Uri $uri = null, mixed $filters = null, mixed $streamToFile = null): Request
184    {
185        return new self($uri, $filters, $streamToFile);
186    }
187
188    /**
189     * Factory to create a new request object with a base path reference for the request URI
190     *
191     * @param  string $basePath
192     * @param  mixed  $filters
193     * @param  mixed  $streamToFile
194     * @return Request
195     */
196    public static function createWithBasePath(string $basePath, mixed $filters = null, mixed $streamToFile = null): Request
197    {
198        return new self(new Uri(null, $basePath), $filters, $streamToFile);
199    }
200
201    /**
202     * Set the auth object
203     *
204     * @param  Auth $auth
205     * @return Request
206     */
207    public function setAuth(Auth $auth): Request
208    {
209        $this->auth = $auth;
210        return $this;
211    }
212
213    /**
214     * Get the auth object
215     *
216     * @return Auth
217     */
218    public function getAuth(): Auth
219    {
220        return $this->auth;
221    }
222
223    /**
224     * Has auth object
225     *
226     * @return bool
227     */
228    public function hasAuth(): bool
229    {
230        return ($this->auth !== null);
231    }
232
233    /**
234     * Get the parsed Accept header
235     *
236     * @return AcceptHeader
237     */
238    public function getAccept(): AcceptHeader
239    {
240        return new AcceptHeader($this->getHeaderLine('Accept'));
241    }
242
243    /**
244     * Whether the request's Accept header accepts the given media type(s)
245     *
246     * @param  string|array      $types
247     * @param  AcceptSpecificity $specificity
248     * @return bool
249     */
250    public function accepts(string|array $types, AcceptSpecificity $specificity = AcceptSpecificity::Any): bool
251    {
252        return $this->getAccept()->accepts($types, $specificity);
253    }
254
255    /**
256     * Given the media types this server can respond with, return the client's best match
257     * (or null if none of them are acceptable)
258     *
259     * @param  array             $available
260     * @param  AcceptSpecificity $specificity
261     * @return string|null
262     */
263    public function getPreferredType(array $available, AcceptSpecificity $specificity = AcceptSpecificity::Any): ?string
264    {
265        return $this->getAccept()->getPreferredType($available, $specificity);
266    }
267
268    /**
269     * Whether the request accepts an HTML response
270     *
271     * Defaults to AcceptSpecificity::Loose - a bare '*\/*' (no Accept header, or a generic
272     * client default like curl's) is not treated as a real preference for HTML.
273     *
274     * @param  AcceptSpecificity $specificity
275     * @return bool
276     */
277    public function acceptsHtml(AcceptSpecificity $specificity = AcceptSpecificity::Loose): bool
278    {
279        return $this->accepts('text/html', $specificity);
280    }
281
282    /**
283     * Whether the request accepts a JSON response
284     *
285     * Defaults to AcceptSpecificity::Loose - a bare '*\/*' (no Accept header, or a generic
286     * client default like curl's) is not treated as a real preference for JSON.
287     *
288     * @param  AcceptSpecificity $specificity
289     * @return bool
290     */
291    public function acceptsJson(AcceptSpecificity $specificity = AcceptSpecificity::Loose): bool
292    {
293        return $this->accepts('application/json', $specificity);
294    }
295
296    /**
297     * Whether the request accepts an XML response (either canonical XML media type)
298     *
299     * Defaults to AcceptSpecificity::Loose - a bare '*\/*' (no Accept header, or a generic
300     * client default like curl's) is not treated as a real preference for XML.
301     *
302     * @param  AcceptSpecificity $specificity
303     * @return bool
304     */
305    public function acceptsXml(AcceptSpecificity $specificity = AcceptSpecificity::Loose): bool
306    {
307        return $this->accepts(['application/xml', 'text/xml'], $specificity);
308    }
309
310    /**
311     * Return a new request with the specified request method
312     *
313     * @param  string $method
314     * @return static
315     */
316    public function withMethod(string $method): static
317    {
318        $clone = clone $this;
319        $clone->methodOverride = $method;
320        return $clone;
321    }
322
323    /**
324     * Return whether or not the method is GET
325     *
326     * @return bool
327     */
328    public function isGet(): bool
329    {
330        return ($this->getMethod() == 'GET');
331    }
332
333    /**
334     * Return whether or not the method is HEAD
335     *
336     * @return bool
337     */
338    public function isHead(): bool
339    {
340        return ($this->getMethod() == 'HEAD');
341    }
342
343    /**
344     * Return whether or not the method is POST
345     *
346     * @return bool
347     */
348    public function isPost(): bool
349    {
350        return ($this->getMethod() == 'POST');
351    }
352
353    /**
354     * Return whether or not the method is PUT
355     *
356     * @return bool
357     */
358    public function isPut(): bool
359    {
360        return ($this->getMethod() == 'PUT');
361    }
362
363    /**
364     * Return whether or not the method is DELETE
365     *
366     * @return bool
367     */
368    public function isDelete(): bool
369    {
370        return ($this->getMethod() == 'DELETE');
371    }
372
373    /**
374     * Return whether or not the method is TRACE
375     *
376     * @return bool
377     */
378    public function isTrace(): bool
379    {
380        return ($this->getMethod() == 'TRACE');
381    }
382
383    /**
384     * Return whether or not the method is OPTIONS
385     *
386     * @return bool
387     */
388    public function isOptions(): bool
389    {
390        return ($this->getMethod() == 'OPTIONS');
391    }
392
393    /**
394     * Return whether or not the method is CONNECT
395     *
396     * @return bool
397     */
398    public function isConnect(): bool
399    {
400        return ($this->getMethod() == 'CONNECT');
401    }
402
403    /**
404     * Return whether or not the method is PATCH
405     *
406     * @return bool
407     */
408    public function isPatch(): bool
409    {
410        return ($this->getMethod() == 'PATCH');
411    }
412
413    /**
414     * Return whether or not the request is secure
415     *
416     * @return bool
417     */
418    public function isSecure(): bool
419    {
420        return (isset($this->server['HTTPS']) || (isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] == '443')));
421    }
422
423    /**
424     * Get the document root
425     *
426     * @return string|null
427     */
428    public function getDocumentRoot(): string|null
429    {
430        return $this->server['DOCUMENT_ROOT'] ?? null;
431    }
432
433    /**
434     * Get the method
435     *
436     * @return string
437     */
438    public function getMethod(): string
439    {
440        return $this->methodOverride ?? ($this->server['REQUEST_METHOD'] ?? 'GET');
441    }
442
443    /**
444     * Get the server port
445     *
446     * @return string|null
447     */
448    public function getPort(): string|null
449    {
450        return isset($this->server['SERVER_PORT']) ? (string)$this->server['SERVER_PORT'] : null;
451    }
452
453    /**
454     * Get scheme
455     *
456     * @return string
457     */
458    public function getScheme(): string
459    {
460        return ($this->isSecure()) ? 'https' : 'http';
461    }
462
463    /**
464     * Get host (without port)
465     *
466     * @return string
467     */
468    public function getHost(): string
469    {
470        $hostname = null;
471
472        if (!empty($this->server['HTTP_HOST'])) {
473            $hostname = $this->server['HTTP_HOST'];
474        } else if (!empty($this->server['SERVER_NAME'])) {
475            $hostname = $this->server['SERVER_NAME'];
476        }
477
478        if (str_contains($hostname, ':')) {
479            $hostname = substr($hostname, 0, strpos($hostname, ':'));
480        }
481
482        return $hostname;
483    }
484
485    /**
486     * Get host with port
487     *
488     * @return string
489     */
490    public function getFullHost(): string
491    {
492        $port     = $this->getPort();
493        $hostname = null;
494
495        if (!empty($this->server['HTTP_HOST'])) {
496            $hostname = $this->server['HTTP_HOST'];
497        } else if (!empty($this->server['SERVER_NAME'])) {
498            $hostname = $this->server['SERVER_NAME'];
499        }
500
501        if ((!str_contains($hostname, ':')) && ($port !== null)) {
502            $hostname .= ':' . $port;
503        }
504
505        return $hostname;
506    }
507
508    /**
509     * Get client's IP
510     *
511     * @param  bool $proxy
512     * @return string
513     */
514    public function getIp(bool $proxy = true): string
515    {
516        $ip = null;
517
518        if ($proxy && isset($this->server['HTTP_CLIENT_IP'])) {
519            $ip = $this->server['HTTP_CLIENT_IP'];
520        } else if ($proxy && isset($this->server['HTTP_X_FORWARDED_FOR'])) {
521            $ip = $this->server['HTTP_X_FORWARDED_FOR'];
522        } else if (isset($this->server['REMOTE_ADDR'])) {
523            $ip = $this->server['REMOTE_ADDR'];
524        }
525
526        return $ip;
527    }
528
529    /**
530     * Get a value from $_COOKIE, or the whole array
531     *
532     * @param  ?string $key
533     * @return string|array|null
534     */
535    public function getCookie(?string $key = null): string|array|null
536    {
537        if ($key === null) {
538            return $this->cookie;
539        } else {
540            return $this->cookie[$key] ?? null;
541        }
542    }
543
544    /**
545     * Get a value from $_SERVER, or the whole array
546     *
547     * @param  ?string $key
548     * @return string|array|null
549     */
550    public function getServer(?string $key = null): string|array|null
551    {
552        if ($key === null) {
553            return $this->server;
554        } else {
555            return $this->server[$key] ?? null;
556        }
557    }
558
559    /**
560     * Get a value from $_ENV, or the whole array
561     *
562     * @param  ?string $key
563     * @return string|array|null
564     */
565    public function getEnv(?string $key = null): string|array|null
566    {
567        if ($key === null) {
568            return $this->env;
569        } else {
570            return $this->env[$key] ?? null;
571        }
572    }
573
574    /**
575     * Get the base path
576     *
577     * @return string
578     */
579    public function getBasePath(): string
580    {
581        return $this->uri->getBasePath();
582    }
583
584    /**
585     * Get the request URI
586     *
587     * @return string
588     */
589    public function getUriString(): string
590    {
591        return $this->uri->getUri();
592    }
593
594    /**
595     * Get the full request URI, including base path
596     *
597     * @return string
598     */
599    public function getFullUriString(): string
600    {
601        return $this->uri->getFullUri();
602    }
603
604    /**
605     * Get a path segment, divided by the forward slash,
606     * where $i refers to the array key index, i.e.,
607     *    0     1     2
608     * /hello/world/page
609     *
610     * @param  int $i
611     * @return string|null
612     */
613    public function getSegment(int $i): string|null
614    {
615        return $this->uri->getSegment($i);
616    }
617
618    /**
619     * Get all path segments
620     *
621     * @return array
622     */
623    public function getSegments(): array
624    {
625        return $this->uri->getSegments();
626    }
627
628    /**
629     * Set the base path
630     *
631     * @param  ?string $path
632     * @return Request
633     */
634    public function setBasePath(?string $path = null): Request
635    {
636        if ($this->uri !== null) {
637            $this->uri->setBasePath($path);
638        }
639        return $this;
640    }
641
642    /**
643     * Return whether or not the request has FILES
644     *
645     * @return bool
646     */
647    public function hasFiles(): bool
648    {
649        return $this->data->hasFiles();
650    }
651
652    /**
653     * Get a value from $_GET, or the whole array
654     *
655     * @param  ?string $key
656     * @return string|array|null
657     */
658    public function getQuery(?string $key = null): string|array|null
659    {
660        return $this->data->getQuery($key);
661    }
662
663    /**
664     * Get a value from $_POST, or the whole array
665     *
666     * @param  ?string $key
667     * @return string|array|null
668     */
669    public function getPost(?string $key = null): string|array|null
670    {
671        return $this->data->getPost($key);
672    }
673
674    /**
675     * Get a value from $_FILES, or the whole array
676     *
677     * @param  ?string $key
678     * @return string|array|null
679     */
680    public function getFiles(?string $key = null): string|array|null
681    {
682        return $this->data->getFiles($key);
683    }
684
685    /**
686     * Get a value from PUT query data, or the whole array
687     *
688     * @param  ?string $key
689     * @return string|array|null
690     */
691    public function getPut(?string $key = null): string|array|null
692    {
693        return $this->data->getPut($key);
694    }
695
696    /**
697     * Get a value from PATCH query data, or the whole array
698     *
699     * @param  ?string $key
700     * @return string|array|null
701     */
702    public function getPatch(?string $key = null): string|array|null
703    {
704        return $this->data->getPatch($key);
705    }
706
707    /**
708     * Get a value from DELETE query data, or the whole array
709     *
710     * @param  ?string $key
711     * @return string|array|null
712     */
713    public function getDelete(?string $key = null): string|array|null
714    {
715        return $this->data->getDelete($key);
716    }
717
718
719    /**
720     * Get a value from query data, or the whole array
721     *
722     * @param  ?string $key
723     * @return string|array|null
724     * @deprecated This always returns null now: the QUERY_STRING re-parse that used to populate
725     *             $queryData was removed. Use getQuery() instead, which reads directly from PHP's
726     *             native $_GET.
727     */
728    public function getQueryData(?string $key = null): string|array|null
729    {
730        return $this->data->getQueryData($key);
731    }
732
733    /**
734     * Has query data
735     *
736     * @return bool
737     * @deprecated This always returns false now: the QUERY_STRING re-parse that used to populate
738     *             $queryData was removed. Check getQuery() instead (e.g. !empty($this->getQuery())),
739     *             which reads directly from PHP's native $_GET.
740     */
741    public function hasQueryData(): bool
742    {
743        return $this->data->hasQueryData();
744    }
745
746    /**
747     * Get a value from parsed data, or the whole array
748     *
749     * @param  ?string $key
750     * @return string|array|null
751     */
752    public function getParsedData(?string $key = null): string|array|null
753    {
754        return $this->data->getParsedData($key);
755    }
756
757    /**
758     * Has parsed data
759     *
760     * @return bool
761     */
762    public function hasParsedData(): bool
763    {
764        return $this->data->hasParsedData();
765    }
766
767    /**
768     * Get the raw data
769     *
770     * @return string|null
771     */
772    public function getRawData(): string|null
773    {
774        return $this->data->getRawData();
775    }
776
777    /**
778     * Has raw data
779     *
780     * @return bool
781     */
782    public function hasRawData(): bool
783    {
784        return $this->data->hasRawData();
785    }
786
787    /**
788     * Get data
789     *
790     * @return Data
791     */
792    public function getData(): Data
793    {
794        return $this->data;
795    }
796
797    /**
798     * Has data
799     *
800     * @return bool
801     */
802    public function hasData(): bool
803    {
804        return ($this->data !== null);
805    }
806
807    /**
808     * Get the server params, per PSR-7 (alias of getServer())
809     *
810     * @return array
811     */
812    public function getServerParams(): array
813    {
814        return $this->server;
815    }
816
817    /**
818     * Get the cookie params, per PSR-7
819     *
820     * @return array
821     */
822    public function getCookieParams(): array
823    {
824        return $this->cookieParamsOverride ?? $this->cookie;
825    }
826
827    /**
828     * Return an instance with the specified cookie params, per PSR-7
829     *
830     * @param  array $cookies
831     * @return static
832     */
833    public function withCookieParams(array $cookies): static
834    {
835        $clone                       = clone $this;
836        $clone->cookieParamsOverride = $cookies;
837        return $clone;
838    }
839
840    /**
841     * Get the query params, per PSR-7
842     *
843     * @return array
844     */
845    public function getQueryParams(): array
846    {
847        if ($this->queryParamsOverride !== null) {
848            return $this->queryParamsOverride;
849        }
850
851        $query = $this->data->getQuery();
852        return is_array($query) ? $query : [];
853    }
854
855    /**
856     * Return an instance with the specified query params, per PSR-7
857     *
858     * @param  array $query
859     * @return static
860     */
861    public function withQueryParams(array $query): static
862    {
863        $clone                      = clone $this;
864        $clone->queryParamsOverride = $query;
865        return $clone;
866    }
867
868    /**
869     * Get the uploaded files, per PSR-7 - an array of UploadedFileInterface instances
870     * built from the native $_FILES-shaped data, unless overridden
871     *
872     * @return array
873     */
874    public function getUploadedFiles(): array
875    {
876        if ($this->uploadedFilesOverride !== null) {
877            return $this->uploadedFilesOverride;
878        }
879
880        return UploadedFile::createFromFilesArray($this->data->getFiles());
881    }
882
883    /**
884     * Return an instance with the specified uploaded files, per PSR-7
885     *
886     * @param  array $uploadedFiles
887     * @return static
888     */
889    public function withUploadedFiles(array $uploadedFiles): static
890    {
891        $clone                        = clone $this;
892        $clone->uploadedFilesOverride = $uploadedFiles;
893        return $clone;
894    }
895
896    /**
897     * Get the parsed body, per PSR-7
898     *
899     * @return array|object|null
900     */
901    public function getParsedBody(): array|object|null
902    {
903        if ($this->parsedBodyOverridden) {
904            return $this->parsedBodyOverride;
905        }
906
907        $parsed = $this->data->getParsedData();
908        return (!empty($parsed)) ? $parsed : null;
909    }
910
911    /**
912     * Return an instance with the specified parsed body, per PSR-7
913     *
914     * @param  array|object|null $data
915     * @return static
916     */
917    public function withParsedBody($data): static
918    {
919        $clone                        = clone $this;
920        $clone->parsedBodyOverride    = $data;
921        $clone->parsedBodyOverridden  = true;
922        return $clone;
923    }
924
925    /**
926     * Get all request attributes, per PSR-7
927     *
928     * @return array
929     */
930    public function getAttributes(): array
931    {
932        return $this->attributes;
933    }
934
935    /**
936     * Get a request attribute, per PSR-7
937     *
938     * @param  string $name
939     * @param  mixed  $default
940     * @return mixed
941     */
942    public function getAttribute(string $name, mixed $default = null): mixed
943    {
944        return $this->attributes[$name] ?? $default;
945    }
946
947    /**
948     * Return an instance with the specified attribute set, per PSR-7
949     *
950     * @param  string $name
951     * @param  mixed  $value
952     * @return static
953     */
954    public function withAttribute(string $name, mixed $value): static
955    {
956        $clone                    = clone $this;
957        $clone->attributes[$name] = $value;
958        return $clone;
959    }
960
961    /**
962     * Return an instance without the specified attribute, per PSR-7
963     *
964     * @param  string $name
965     * @return static
966     */
967    public function withoutAttribute(string $name): static
968    {
969        $clone = clone $this;
970        unset($clone->attributes[$name]);
971        return $clone;
972    }
973
974    /**
975     * Magic method to get a value from one of the server/environment variables
976     *
977     * @param  string $name
978     * @return mixed
979     */
980    public function __get(string $name): mixed
981    {
982        return match ($name) {
983            'get'     => $this->data->get,
984            'post'    => $this->data->post,
985            'files'   => $this->data->files,
986            'put'     => $this->data->put,
987            'patch'   => $this->data->patch,
988            'delete'  => $this->data->delete,
989            'parsed'  => $this->data->parsed,
990            'raw'     => $this->data->raw,
991            'cookie'  => $this->cookie,
992            'server'  => $this->server,
993            'env'     => $this->env,
994            'headers' => $this->headers,
995            default   => null,
996        };
997    }
998
999}