Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.12% covered (success)
99.12%
113 / 114
97.92% covered (success)
97.92%
47 / 48
CRAP
0.00% covered (danger)
0.00%
0 / 1
Request
99.12% covered (success)
99.12%
113 / 114
97.92% covered (success)
97.92%
47 / 48
99
0.00% covered (danger)
0.00%
0 / 1
 __construct
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
11
 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
 isGet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isHead
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isPost
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isPut
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isDelete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isTrace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isOptions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isConnect
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isPatch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 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
1
 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
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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
 __get
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
14
1<?php
2/**
3 * Pop PHP Framework (https://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@noladev.com>
7 * @copyright  Copyright (c) 2009-2025 NOLA Interactive, LLC.
8 * @license    https://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Http\Server;
15
16use Pop\Http\Auth;
17use Pop\Http\Uri;
18use Pop\Http\AbstractRequest;
19use Pop\Mime\Part\Body;
20
21/**
22 * HTTP server request class
23 *
24 * @category   Pop
25 * @package    Pop\Http
26 * @author     Nick Sagona, III <dev@noladev.com>
27 * @copyright  Copyright (c) 2009-2025 NOLA Interactive, LLC.
28 * @license    https://www.popphp.org/license     New BSD License
29 * @version    5.3.2
30 */
31class Request extends AbstractRequest
32{
33
34    /**
35     * Server request data object
36     * @var ?Data
37     */
38    protected ?Data $data = null;
39
40    /**
41     * COOKIE array
42     * @var array
43     */
44    protected array $cookie = [];
45
46    /**
47     * SERVER array
48     * @var array
49     */
50    protected array $server = [];
51
52    /**
53     * ENV array
54     * @var array
55     */
56    protected array $env = [];
57
58    /**
59     * HTTP auth object
60     * @var ?Auth
61     */
62    protected ?Auth $auth = null;
63
64    /**
65     * Constructor
66     *
67     * Instantiate the request object
68     *
69     * @param  Uri|string|null $uri
70     * @param  mixed           $filters
71     * @param  mixed           $streamToFile
72     * @throws Exception|\Pop\Http\Exception
73     */
74    public function __construct(Uri|string|null $uri = null, mixed $filters = null, mixed $streamToFile = null)
75    {
76        parent::__construct($uri);
77
78        $this->cookie = (isset($_COOKIE)) ? $_COOKIE : [];
79        $this->server = (isset($_SERVER)) ? $_SERVER : [];
80        $this->env    = (isset($_ENV))    ? $_ENV    : [];
81
82        // Get any possible request headers
83        if (function_exists('getallheaders')) {
84            $this->addHeaders(getallheaders());
85        } else {
86            foreach ($_SERVER as $key => $value) {
87                if (str_starts_with($key, 'HTTP_')) {
88                    $key = ucfirst(strtolower(str_replace('HTTP_', '', $key)));
89                    if (str_contains($key, '_')) {
90                        $ary = explode('_', $key);
91                        foreach ($ary as $k => $v){
92                            $ary[$k] = ucfirst(strtolower($v));
93                        }
94                        $key = implode('-', $ary);
95                    }
96                    $this->addHeader($key, $value);
97                }
98            }
99        }
100
101        if ($this->hasHeader('Authorization')) {
102            $this->setAuth(Auth::parse($this->getHeader('Authorization')));
103        }
104
105        $this->data = new Data(
106            $this->getHeaderValue('Content-Type'), $this->getHeaderValue('Content-Encoding'), $filters, $streamToFile
107        );
108
109        if ($this->data->hasRawData()) {
110            $this->body = new Body($this->data->getRawData());
111        }
112    }
113
114    /**
115     * Factory to create a new request object
116     *
117     * @param  ?Uri  $uri
118     * @param  mixed $filters
119     * @param  mixed $streamToFile
120     * @throws Exception|\Pop\Http\Exception
121     * @return Request
122     */
123    public static function create(?Uri $uri = null, mixed $filters = null, mixed $streamToFile = null): Request
124    {
125        return new self($uri, $filters, $streamToFile);
126    }
127
128    /**
129     * Factory to create a new request object with a base path reference for the request URI
130     *
131     * @param  string $basePath
132     * @param  mixed  $filters
133     * @param  mixed  $streamToFile
134     * @return Request
135     */
136    public static function createWithBasePath(string $basePath, mixed $filters = null, mixed $streamToFile = null): Request
137    {
138        return new self(new Uri(null, $basePath), $filters, $streamToFile);
139    }
140
141    /**
142     * Set the auth object
143     *
144     * @param  Auth $auth
145     * @return Request
146     */
147    public function setAuth(Auth $auth): Request
148    {
149        $this->auth = $auth;
150        return $this;
151    }
152
153    /**
154     * Get the auth object
155     *
156     * @return Auth
157     */
158    public function getAuth(): Auth
159    {
160        return $this->auth;
161    }
162
163    /**
164     * Has auth object
165     *
166     * @return bool
167     */
168    public function hasAuth(): bool
169    {
170        return ($this->auth !== null);
171    }
172
173    /**
174     * Return whether or not the method is GET
175     *
176     * @return bool
177     */
178    public function isGet(): bool
179    {
180        return (isset($this->server['REQUEST_METHOD']) && ($this->server['REQUEST_METHOD'] == 'GET'));
181    }
182
183    /**
184     * Return whether or not the method is HEAD
185     *
186     * @return bool
187     */
188    public function isHead(): bool
189    {
190        return (isset($this->server['REQUEST_METHOD']) && ($this->server['REQUEST_METHOD'] == 'HEAD'));
191    }
192
193    /**
194     * Return whether or not the method is POST
195     *
196     * @return bool
197     */
198    public function isPost(): bool
199    {
200        return (isset($this->server['REQUEST_METHOD']) && ($this->server['REQUEST_METHOD'] == 'POST'));
201    }
202
203    /**
204     * Return whether or not the method is PUT
205     *
206     * @return bool
207     */
208    public function isPut(): bool
209    {
210        return (isset($this->server['REQUEST_METHOD']) && ($this->server['REQUEST_METHOD'] == 'PUT'));
211    }
212
213    /**
214     * Return whether or not the method is DELETE
215     *
216     * @return bool
217     */
218    public function isDelete(): bool
219    {
220        return (isset($this->server['REQUEST_METHOD']) && ($this->server['REQUEST_METHOD'] == 'DELETE'));
221    }
222
223    /**
224     * Return whether or not the method is TRACE
225     *
226     * @return bool
227     */
228    public function isTrace(): bool
229    {
230        return (isset($this->server['REQUEST_METHOD']) && ($this->server['REQUEST_METHOD'] == 'TRACE'));
231    }
232
233    /**
234     * Return whether or not the method is OPTIONS
235     *
236     * @return bool
237     */
238    public function isOptions(): bool
239    {
240        return (isset($this->server['REQUEST_METHOD']) && ($this->server['REQUEST_METHOD'] == 'OPTIONS'));
241    }
242
243    /**
244     * Return whether or not the method is CONNECT
245     *
246     * @return bool
247     */
248    public function isConnect(): bool
249    {
250        return (isset($this->server['REQUEST_METHOD']) && ($this->server['REQUEST_METHOD'] == 'CONNECT'));
251    }
252
253    /**
254     * Return whether or not the method is PATCH
255     *
256     * @return bool
257     */
258    public function isPatch(): bool
259    {
260        return (isset($this->server['REQUEST_METHOD']) && ($this->server['REQUEST_METHOD'] == 'PATCH'));
261    }
262
263    /**
264     * Return whether or not the request is secure
265     *
266     * @return bool
267     */
268    public function isSecure(): bool
269    {
270        return (isset($this->server['HTTPS']) || (isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] == '443')));
271    }
272
273    /**
274     * Get the document root
275     *
276     * @return string|null
277     */
278    public function getDocumentRoot(): string|null
279    {
280        return $this->server['DOCUMENT_ROOT'] ?? null;
281    }
282
283    /**
284     * Get the method
285     *
286     * @return string|null
287     */
288    public function getMethod(): string|null
289    {
290        return $this->server['REQUEST_METHOD'] ?? null;
291    }
292
293    /**
294     * Get the server port
295     *
296     * @return string|null
297     */
298    public function getPort(): string|null
299    {
300        return $this->server['SERVER_PORT'] ?? null;
301    }
302
303    /**
304     * Get scheme
305     *
306     * @return string
307     */
308    public function getScheme(): string
309    {
310        return ($this->isSecure()) ? 'https' : 'http';
311    }
312
313    /**
314     * Get host (without port)
315     *
316     * @return string
317     */
318    public function getHost(): string
319    {
320        $hostname = null;
321
322        if (!empty($this->server['HTTP_HOST'])) {
323            $hostname = $this->server['HTTP_HOST'];
324        } else if (!empty($this->server['SERVER_NAME'])) {
325            $hostname = $this->server['SERVER_NAME'];
326        }
327
328        if (str_contains($hostname, ':')) {
329            $hostname = substr($hostname, 0, strpos($hostname, ':'));
330        }
331
332        return $hostname;
333    }
334
335    /**
336     * Get host with port
337     *
338     * @return string
339     */
340    public function getFullHost(): string
341    {
342        $port     = $this->getPort();
343        $hostname = null;
344
345        if (!empty($this->server['HTTP_HOST'])) {
346            $hostname = $this->server['HTTP_HOST'];
347        } else if (!empty($this->server['SERVER_NAME'])) {
348            $hostname = $this->server['SERVER_NAME'];
349        }
350
351        if ((!str_contains($hostname, ':')) && ($port !== null)) {
352            $hostname .= ':' . $port;
353        }
354
355        return $hostname;
356    }
357
358    /**
359     * Get client's IP
360     *
361     * @param  bool $proxy
362     * @return string
363     */
364    public function getIp(bool $proxy = true): string
365    {
366        $ip = null;
367
368        if ($proxy && isset($this->server['HTTP_CLIENT_IP'])) {
369            $ip = $this->server['HTTP_CLIENT_IP'];
370        } else if ($proxy && isset($this->server['HTTP_X_FORWARDED_FOR'])) {
371            $ip = $this->server['HTTP_X_FORWARDED_FOR'];
372        } else if (isset($this->server['REMOTE_ADDR'])) {
373            $ip = $this->server['REMOTE_ADDR'];
374        }
375
376        return $ip;
377    }
378
379    /**
380     * Get a value from $_COOKIE, or the whole array
381     *
382     * @param  ?string $key
383     * @return string|array|null
384     */
385    public function getCookie(?string $key = null): string|array|null
386    {
387        if ($key === null) {
388            return $this->cookie;
389        } else {
390            return $this->cookie[$key] ?? null;
391        }
392    }
393
394    /**
395     * Get a value from $_SERVER, or the whole array
396     *
397     * @param  ?string $key
398     * @return string|array|null
399     */
400    public function getServer(?string $key = null): string|array|null
401    {
402        if ($key === null) {
403            return $this->server;
404        } else {
405            return $this->server[$key] ?? null;
406        }
407    }
408
409    /**
410     * Get a value from $_ENV, or the whole array
411     *
412     * @param  ?string $key
413     * @return string|array|null
414     */
415    public function getEnv(?string $key = null): string|array|null
416    {
417        if ($key === null) {
418            return $this->env;
419        } else {
420            return $this->env[$key] ?? null;
421        }
422    }
423
424    /**
425     * Get the base path
426     *
427     * @return string
428     */
429    public function getBasePath(): string
430    {
431        return $this->uri->getBasePath();
432    }
433
434    /**
435     * Get the request URI
436     *
437     * @return string
438     */
439    public function getUriString(): string
440    {
441        return $this->uri->getUri();
442    }
443
444    /**
445     * Get the full request URI, including base path
446     *
447     * @return string
448     */
449    public function getFullUriString(): string
450    {
451        return $this->uri->getFullUri();
452    }
453
454    /**
455     * Get a path segment, divided by the forward slash,
456     * where $i refers to the array key index, i.e.,
457     *    0     1     2
458     * /hello/world/page
459     *
460     * @param  int $i
461     * @return string|null
462     */
463    public function getSegment(int $i): string|null
464    {
465        return $this->uri->getSegment($i);
466    }
467
468    /**
469     * Get all path segments
470     *
471     * @return array
472     */
473    public function getSegments(): array
474    {
475        return $this->uri->getSegments();
476    }
477
478    /**
479     * Set the base path
480     *
481     * @param  ?string $path
482     * @return Request
483     */
484    public function setBasePath(?string $path = null): Request
485    {
486        if ($this->uri !== null) {
487            $this->uri->setBasePath($path);
488        }
489        return $this;
490    }
491
492    /**
493     * Return whether or not the request has FILES
494     *
495     * @return bool
496     */
497    public function hasFiles(): bool
498    {
499        return $this->data->hasFiles();
500    }
501
502    /**
503     * Get a value from $_GET, or the whole array
504     *
505     * @param  ?string $key
506     * @return string|array|null
507     */
508    public function getQuery(?string $key = null): string|array|null
509    {
510        return $this->data->getQuery($key);
511    }
512
513    /**
514     * Get a value from $_POST, or the whole array
515     *
516     * @param  ?string $key
517     * @return string|array|null
518     */
519    public function getPost(?string $key = null): string|array|null
520    {
521        return $this->data->getPost($key);
522    }
523
524    /**
525     * Get a value from $_FILES, or the whole array
526     *
527     * @param  ?string $key
528     * @return string|array|null
529     */
530    public function getFiles(?string $key = null): string|array|null
531    {
532        return $this->data->getFiles($key);
533    }
534
535    /**
536     * Get a value from PUT query data, or the whole array
537     *
538     * @param  ?string $key
539     * @return string|array|null
540     */
541    public function getPut(?string $key = null): string|array|null
542    {
543        return $this->data->getPut($key);
544    }
545
546    /**
547     * Get a value from PATCH query data, or the whole array
548     *
549     * @param  ?string $key
550     * @return string|array|null
551     */
552    public function getPatch(?string $key = null): string|array|null
553    {
554        return $this->data->getPatch($key);
555    }
556
557    /**
558     * Get a value from DELETE query data, or the whole array
559     *
560     * @param  ?string $key
561     * @return string|array|null
562     */
563    public function getDelete(?string $key = null): string|array|null
564    {
565        return $this->data->getDelete($key);
566    }
567
568
569    /**
570     * Get a value from query data, or the whole array
571     *
572     * @param  ?string $key
573     * @return string|array|null
574     */
575    public function getQueryData(?string $key = null): string|array|null
576    {
577        return $this->data->getQueryData($key);
578    }
579
580    /**
581     * Has query data
582     *
583     * @return bool
584     */
585    public function hasQueryData(): bool
586    {
587        return $this->data->hasQueryData();
588    }
589
590    /**
591     * Get a value from parsed data, or the whole array
592     *
593     * @param  ?string $key
594     * @return string|array|null
595     */
596    public function getParsedData(?string $key = null): string|array|null
597    {
598        return $this->data->getParsedData($key);
599    }
600
601    /**
602     * Has parsed data
603     *
604     * @return bool
605     */
606    public function hasParsedData(): bool
607    {
608        return $this->data->hasParsedData();
609    }
610
611    /**
612     * Get the raw data
613     *
614     * @return string|null
615     */
616    public function getRawData(): string|null
617    {
618        return $this->data->getRawData();
619    }
620
621    /**
622     * Has raw data
623     *
624     * @return bool
625     */
626    public function hasRawData(): bool
627    {
628        return $this->data->hasRawData();
629    }
630
631    /**
632     * Get data
633     *
634     * @return Uri
635     */
636    public function getData(): Data
637    {
638        return $this->data;
639    }
640
641    /**
642     * Has data
643     *
644     * @return bool
645     */
646    public function hasData(): bool
647    {
648        return ($this->data !== null);
649    }
650
651    /**
652     * Magic method to get a value from one of the server/environment variables
653     *
654     * @param  string $name
655     * @return mixed
656     */
657    public function __get(string $name): mixed
658    {
659        return match ($name) {
660            'get'     => $this->data->get,
661            'post'    => $this->data->post,
662            'files'   => $this->data->files,
663            'put'     => $this->data->put,
664            'patch'   => $this->data->patch,
665            'delete'  => $this->data->delete,
666            'parsed'  => $this->data->parsed,
667            'raw'     => $this->data->raw,
668            'cookie'  => $this->cookie,
669            'server'  => $this->server,
670            'env'     => $this->env,
671            'headers' => $this->headers,
672            default   => null,
673        };
674    }
675
676}