Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.19% covered (success)
85.19%
138 / 162
79.17% covered (success)
79.17%
38 / 48
CRAP
0.00% covered (danger)
0.00%
0 / 1
Request
85.19% covered (success)
85.19%
138 / 162
79.17% covered (success)
79.17%
38 / 48
144.23
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createJson
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createXml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createUrlEncoded
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createMultipart
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMethod
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 getMethod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasMethod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clearMethod
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 addHeader
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
9
 setNoContentLength
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 isNoContentLength
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRawData
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 useRawData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setQuery
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 addQuery
75.00% covered (success)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
3.14
 getQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasQuery
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeQuery
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 removeAllQuery
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setData
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 addData
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
3.33
 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
 removeData
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 removeAllData
83.33% covered (success)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getUriAsString
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
10
 setRequestType
68.75% covered (warning)
68.75%
11 / 16
0.00% covered (danger)
0.00%
0 / 1
11.47
 getRequestType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasRequestType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeRequestType
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 createAsJson
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 isJson
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createAsXml
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 isXml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createAsUrlEncoded
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 isUrlEncoded
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createAsMultipart
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isMultipart
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createAsCustomType
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isCustomType
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
30
 isValidMethod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasDataContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getDataContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDataContentLength
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepareData
75.00% covered (success)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 __call
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
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-2026 NOLA Interactive, LLC.
8 * @license    https://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Http\Client;
15
16use Pop\Http\Uri;
17use Pop\Http\AbstractRequest;
18use Pop\Mime\Message;
19use Pop\Mime\Part\Header;
20
21/**
22 * HTTP client request class
23 *
24 * @category   Pop
25 * @package    Pop\Http
26 * @author     Nick Sagona, III <dev@noladev.com>
27 * @copyright  Copyright (c) 2009-2026 NOLA Interactive, LLC.
28 * @license    https://www.popphp.org/license     New BSD License
29 * @version    5.3.8
30 */
31class Request extends AbstractRequest
32{
33
34    /**
35     * Request type constants
36     * @var string
37     */
38    const URLENCODED = 'application/x-www-form-urlencoded';
39    const JSON       = 'application/json';
40    const XML        = 'application/xml';
41    const MULTIPART  = 'multipart/form-data';
42
43    /**
44     * Request method
45     * @var ?string
46     */
47    protected ?string $method = null;
48
49    /**
50     * Request type
51     * @var ?string
52     */
53    protected ?string $requestType = null;
54
55    /**
56     * No Content-Length header flag
57     * @var bool
58     */
59    protected bool $noContentLength = false;
60
61    /**
62     * Raw data flag
63     * @var bool
64     */
65    protected bool $rawData = false;
66
67    /**
68     * Client request query data object
69     *
70     * Can only be a URL-encoded query string on the URI
71     *
72     * @var ?Data
73     */
74    protected ?Data $query = null;
75
76    /**
77     * Client request data object
78     *
79     * Can be any type of supported request data:
80     *     - URL-encoded query string on the URI (GET method)
81     *     - URL-encoded body (any method other than GET)
82     *     - JSON-encoded body
83     *     - XML-encoded body
84     *     - Multipart/form body
85     *
86     * @var ?Data
87     */
88    protected ?Data $data = null;
89
90    /**
91     * Constructor
92     *
93     * Instantiate the request data object
94     *
95     * @param  Uri|string|null $uri
96     * @param  string          $method
97     * @param  mixed           $data
98     * @param  ?string         $type
99     * @param  bool            $strict
100     * @throws Exception|\Pop\Http\Exception
101     */
102    public function __construct(Uri|string|null $uri = null, string $method = 'GET', mixed $data = null, ?string $type = null, bool $strict = false)
103    {
104        parent::__construct($uri);
105
106        if ($method !== null) {
107            $this->setMethod($method, $strict);
108        }
109        if ($data !== null) {
110            $this->setData($data);
111        }
112        if ($type !== null) {
113            $this->setRequestType($type);
114        }
115    }
116
117    /**
118     * Factory method to create a Request object
119     *
120     * @param  Uri|string|null $uri
121     * @param  string          $method
122     * @param  mixed           $data
123     * @param  ?string         $type
124     * @param  bool            $strict
125     * @throws Exception|\Pop\Http\Exception
126     * @return Request
127     */
128    public static function create(Uri|string|null $uri = null, string $method = 'GET', mixed $data = null, ?string $type = null, bool $strict = false): Request
129    {
130        return new self($uri, $method, $data, $type, $strict);
131    }
132
133    /**
134     * Factory method to create a JSON Request object
135     *
136     * @param  Uri|string|null $uri
137     * @param  string          $method
138     * @param  mixed           $data
139     * @param  bool            $strict
140     * @throws Exception|\Pop\Http\Exception
141     * @return Request
142     */
143    public static function createJson(Uri|string|null $uri = null, string $method = 'POST', mixed $data = null, bool $strict = false): Request
144    {
145        return new self($uri, $method, $data, Request::JSON, $strict);
146    }
147
148    /**
149     * Factory method to create an XML Request object
150     *
151     * @param  Uri|string|null $uri
152     * @param  string          $method
153     * @param  mixed           $data
154     * @param  bool            $strict
155     * @throws Exception|\Pop\Http\Exception
156     * @return Request
157     */
158    public static function createXml(Uri|string|null $uri = null, string $method = 'POST', mixed $data = null, bool $strict = false): Request
159    {
160        return new self($uri, $method, $data, Request::XML, $strict);
161    }
162
163    /**
164     * Factory method to create a URL-encoded Request object
165     *
166     * @param  Uri|string|null $uri
167     * @param  string          $method
168     * @param  mixed           $data
169     * @param  bool            $strict
170     * @throws Exception|\Pop\Http\Exception
171     * @return Request
172     */
173    public static function createUrlEncoded(Uri|string|null $uri = null, string $method = 'GET', mixed $data = null, bool $strict = false): Request
174    {
175        return new self($uri, $method, $data, Request::URLENCODED, $strict);
176    }
177
178    /**
179     * Factory method to create a multipart Request object
180     *
181     * @param  Uri|string|null $uri
182     * @param  string          $method
183     * @param  mixed           $data
184     * @param  bool            $strict
185     * @throws Exception|\Pop\Http\Exception
186     * @return Request
187     */
188    public static function createMultipart(Uri|string|null $uri = null, string $method = 'POST', mixed $data = null, bool $strict = false): Request
189    {
190        return new self($uri, $method, $data, Request::MULTIPART, $strict);
191    }
192
193    /**
194     * Set method
195     *
196     * @param  string $method
197     * @param  bool   $strict
198     * @throws Exception
199     * @return Request
200     */
201    public function setMethod(string $method, bool $strict = false): Request
202    {
203        $method = strtoupper($method);
204
205        if ($strict) {
206            if (!$this->isValidMethod($method)) {
207                throw new Exception('Error: That request method is not valid.');
208            }
209        }
210
211        $this->method = $method;
212        return $this;
213    }
214
215    /**
216     * Get method
217     *
218     * @return ?string
219     */
220    public function getMethod(): ?string
221    {
222        return $this->method;
223    }
224
225    /**
226     * Has method
227     *
228     * @return bool
229     */
230    public function hasMethod(): bool
231    {
232        return ($this->method !== null);
233    }
234
235    /**
236     * Clear method
237     *
238     * @return Request
239     */
240    public function clearMethod(): Request
241    {
242        $this->method = null;
243        return $this;
244    }
245
246    /**
247     * Add a header
248     *
249     * @param  Header|string|int $header
250     * @param  ?string           $value
251     * @return Request
252     */
253    public function addHeader(Header|string|int $header, ?string $value = null): Request
254    {
255        $contentType = null;
256        if (is_numeric($header) && ($value !== null)) {
257            $header = Header::parse($value);
258            $value  = null;
259        }
260        if (is_string($header) && ($header == 'Content-Type')) {
261            $contentType = $value;
262        } else if (($header instanceof Header) && ($header->getName() == 'Content-Type')) {
263            $contentType = $header->getValue()->getValue();
264        }
265
266        if (($contentType !== null) && ($this->requestType === null)) {
267            $this->requestType = $contentType;
268        }
269
270        parent::addHeader($header, $value);
271        return $this;
272    }
273
274    /**
275     * Set no Content-Length header flag
276     *
277     * @param  bool $noContentLength
278     * @return Request
279     */
280    public function setNoContentLength(bool $noContentLength): Request
281    {
282        $this->noContentLength = $noContentLength;
283        return $this;
284    }
285
286    /**
287     * Is no Content-Length header flag
288     *
289     * @return bool
290     */
291    public function isNoContentLength(): bool
292    {
293        return $this->noContentLength;
294    }
295
296    /**
297     * Set raw data flag
298     *
299     * @param  bool $rawData
300     * @return Request
301     */
302    public function setRawData(bool $rawData): Request
303    {
304        $this->rawData = $rawData;
305        return $this;
306    }
307
308    /**
309     * Use raw data
310     *
311     * @return bool
312     */
313    public function useRawData(): bool
314    {
315        return $this->rawData;
316    }
317
318    /**
319     * Set query data
320     *
321     * @param  mixed $query
322     * @param  mixed $filters
323     * @return Request
324     */
325    public function setQuery(mixed $query, mixed $filters = null): Request
326    {
327        $this->setRequestType(Request::URLENCODED);
328        $this->query = ($query instanceof Data) ? $query : new Data($query, $filters, $this);
329        return $this;
330    }
331
332    /**
333     * Add query data
334     *
335     * @param  mixed $name
336     * @param  mixed $value
337     * @return Request
338     */
339    public function addQuery(mixed $name, mixed $value): Request
340    {
341        $this->setRequestType(Request::URLENCODED);
342        if ($this->query === null) {
343            $this->setRequestType(Request::URLENCODED);
344            $this->query = new Data([], null, $this);
345        } else if (!$this->query->hasRequest()) {
346            $this->query->setRequest($this);
347        }
348        $this->query->addData($name, $value);
349
350        return $this;
351    }
352
353    /**
354     * Get query data
355     *
356     * @return ?Data
357     */
358    public function getQuery(): ?Data
359    {
360        return $this->query;
361    }
362
363    /**
364     * Has query data
365     *
366     * @return bool
367     */
368    public function hasQuery(): bool
369    {
370        return !empty($this->query);
371    }
372
373    /**
374     * Remove query data
375     *
376     * @param  string $key
377     * @return Request
378     */
379    public function removeQuery(string $key): Request
380    {
381        if ($this->query->hasData($key)) {
382            $this->query->removeData($key);
383        }
384        return $this;
385    }
386
387    /**
388     * Remove all query data
389     *
390     * @return Request
391     */
392    public function removeAllQuery(): Request
393    {
394        $this->query = null;
395
396        if ($this->hasHeader('Content-Type')) {
397            $this->removeHeader('Content-Type');
398        }
399        return $this;
400    }
401
402    /**
403     * Set data
404     *
405     * @param  mixed $data
406     * @param  mixed $filters
407     * @return Request
408     */
409    public function setData(mixed $data, mixed $filters = null): Request
410    {
411        if ($data instanceof Data) {
412            if (!$data->hasRequest()) {
413                $data->setRequest($this);
414            }
415        } else {
416            $data = new Data($data, $filters, $this);
417        }
418
419        $this->data = $data;
420
421        return $this;
422    }
423
424    /**
425     * Add data
426     *
427     * @param  mixed $name
428     * @param  mixed $value
429     * @return Request
430     */
431    public function addData(mixed $name, mixed $value): Request
432    {
433        if ($this->data === null) {
434            $this->data = new Data([], null, $this);
435        } else if (!$this->data->hasRequest()) {
436            $this->data->setRequest($this);
437        }
438        $this->data->addData($name, $value);
439
440        return $this;
441    }
442
443    /**
444     * Get data
445     *
446     * @return ?Data
447     */
448    public function getData(): ?Data
449    {
450        return $this->data;
451    }
452
453    /**
454     * Has data
455     *
456     * @return bool
457     */
458    public function hasData(): bool
459    {
460        return !empty($this->data);
461    }
462
463    /**
464     * Remove data
465     *
466     * @param  string $key
467     * @return Request
468     */
469    public function removeData(string $key): Request
470    {
471        if ($this->data->hasData($key)) {
472            $this->data->removeData($key);
473        }
474        return $this;
475    }
476
477    /**
478     * Remove all data
479     *
480     * @return Request
481     */
482    public function removeAllData(): Request
483    {
484        $this->data = null;
485
486        if ($this->hasHeader('Content-Type')) {
487            $this->removeHeader('Content-Type');
488        }
489        if ($this->hasHeader('Content-Length')) {
490            $this->removeHeader('Content-Length');
491        }
492        return $this;
493    }
494
495    /**
496     * Get full URI as string
497     *
498     * @param  bool $query
499     * @return string
500     */
501    public function getUriAsString(bool $query = true): string
502    {
503        $uri = parent::getUriAsString();
504
505        if ($query) {
506            $queryString = null;
507
508            // If request has generic query data
509            if ($this->hasQuery()) {
510                $queryString = $this->query->prepare()->getDataContent();
511            // Else, if request has explicit data configured to be a query string over GET
512            } else if (($this->method == 'GET') && ($this->hasData()) &&
513                (($this->requestType === null) || ($this->requestType == self::URLENCODED))) {
514                $queryString = $this->data->prepare()->getDataContent();
515            }
516
517            if (!empty($queryString) && !str_contains($uri, $queryString)) {
518                $uri .= ((str_contains($uri, '?')) ? '&' : '?') . $queryString;
519            }
520        }
521
522        return $uri;
523    }
524
525    /**
526     * Set request type
527     *
528     * @param  ?string $type
529     * @param  bool    $handleHeader
530     * @return Request
531     */
532    public function setRequestType(?string $type = null, bool $handleHeader = true): Request
533    {
534        switch ($type) {
535            case self::JSON:
536                $this->createAsJson($type, $handleHeader);
537                break;
538            case self::XML:
539                $this->createAsXml($type, $handleHeader);
540                break;
541            case self::URLENCODED:
542                $this->createAsUrlEncoded($type, $handleHeader);
543                break;
544            case self::MULTIPART:
545                $this->createAsMultipart($type);
546                break;
547            default:
548                // Custom content-types
549                if ($type !== null) {
550                    if (strrpos($type, 'json') !== false) {
551                        $this->createAsJson($type);
552                    } else if (strrpos($type, 'xml') !== false) {
553                        $this->createAsXml($type);
554                    } else {
555                        $this->createAsCustomType($type);
556                    }
557                } else {
558                    $this->removeRequestType($handleHeader);
559                }
560
561        }
562
563        return $this;
564    }
565
566    /**
567     * Get request type
568     *
569     * @return ?string
570     */
571    public function getRequestType(): ?string
572    {
573        return $this->requestType;
574    }
575
576    /**
577     * Has request type
578     *
579     * @return bool
580     */
581    public function hasRequestType(): bool
582    {
583        return ($this->requestType !== null);
584    }
585
586    /**
587     * Remove request type
588     *
589     * @param  bool $removeHeader
590     * @return Request
591     */
592    public function removeRequestType(bool $removeHeader = false): Request
593    {
594        $this->requestType = null;
595        if (($removeHeader) && ($this->hasHeader('Content-Type'))) {
596            $this->removeHeader('Content-Type');
597        }
598        return $this;
599    }
600
601    /**
602     * Create request as JSON
603     *
604     * @param  string $type
605     * @param  bool   $addHeader
606     * @return Request
607     */
608    public function createAsJson(string $type = self::JSON, bool $addHeader = true): Request
609    {
610        $this->requestType = $type;
611
612        if ($this->hasHeader('Content-Type')) {
613            $this->removeHeader('Content-Type');
614        }
615        if ($addHeader) {
616            $this->addHeader('Content-Type', $this->requestType);
617        }
618        $this->addHeader('Content-Type', $this->requestType);
619
620        return $this;
621    }
622
623    /**
624     * Check if request is JSON
625     *
626     * @return bool
627     */
628    public function isJson(): bool
629    {
630        return str_contains(strtolower($this->requestType), 'json');
631    }
632
633    /**
634     * Create request as XML
635     *
636     * @param  string $type
637     * @param  bool   $addHeader
638     * @return Request
639     */
640    public function createAsXml(string $type = self::XML, bool $addHeader = true): Request
641    {
642        $this->requestType = $type;
643
644        if ($this->hasHeader('Content-Type')) {
645            $this->removeHeader('Content-Type');
646        }
647
648        if ($addHeader) {
649            $this->addHeader('Content-Type', $this->requestType);
650        }
651
652        return $this;
653    }
654
655    /**
656     * Check if request is XML
657     *
658     * @return bool
659     */
660    public function isXml(): bool
661    {
662        return str_contains(strtolower($this->requestType), 'xml');
663    }
664
665    /**
666     * Create request as a URL-encoded form
667     *
668     * @param  string $type
669     * @param  bool   $addHeader
670     * @return Request
671     */
672    public function createAsUrlEncoded(string $type = self::URLENCODED, bool $addHeader = true): Request
673    {
674        $this->requestType = $type;
675
676        if ($this->hasHeader('Content-Type')) {
677            $this->removeHeader('Content-Type');
678        }
679
680        if ($addHeader) {
681            $this->addHeader('Content-Type', $this->requestType);
682        }
683
684        return $this;
685    }
686
687    /**
688     * Check if request is a URL-encoded form
689     *
690     * @return bool
691     */
692    public function isUrlEncoded(): bool
693    {
694        return ($this->requestType == self::URLENCODED);
695    }
696
697    /**
698     * Create request as a multipart form
699     *
700     * @param  string $type
701     * @return Request
702     */
703    public function createAsMultipart(string $type = self::MULTIPART): Request
704    {
705        $this->requestType = $type;
706        return $this;
707    }
708
709    /**
710     * Check if request is a multipart form
711     *
712     * @return bool
713     */
714    public function isMultipart(): bool
715    {
716        return str_contains(strtolower($this->requestType), self::MULTIPART);
717    }
718
719    /**
720     * Create request as custom content type
721     *
722     * @param  string $type
723     * @param  bool   $addHeader
724     * @return Request
725     */
726    public function createAsCustomType(string $type, bool $addHeader = true): Request
727    {
728        $this->requestType = $type;
729
730        if ($addHeader) {
731            $this->addHeader('Content-Type', $this->requestType);
732        }
733        return $this;
734    }
735
736    /**
737     * Check if request is a custom content type
738     *
739     * @return bool
740     */
741    public function isCustomType(): bool
742    {
743        return (!empty($this->requestType) && (strrpos($this->requestType, 'json') !== false) &&
744            (strrpos($this->requestType, 'xml') !== false) && ($this->requestType !== self::URLENCODED) &&
745            ($this->requestType !== self::MULTIPART));
746    }
747
748    /**
749     * Is valid method
750     *
751     * @param  string $method
752     * @return bool
753     */
754    public function isValidMethod(string $method): bool
755    {
756        return in_array(strtoupper($method), ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE']);
757    }
758
759    /**
760     * Has data content
761     *
762     * @return bool
763     */
764    public function hasDataContent(): bool
765    {
766        return (($this->data !== null) && ($this->data->hasDataContent()));
767    }
768
769    /**
770     * Get the data content
771     *
772     * @return ?string
773     */
774    public function getDataContent(): ?string
775    {
776        return $this->data?->getDataContent();
777    }
778
779    /**
780     * Get the data content length
781     *
782     * @param  bool $mb
783     * @return int|null
784     */
785    public function getDataContentLength(bool $mb = false): int|null
786    {
787        return $this->data?->getDataContentLength($mb);
788    }
789
790    /**
791     * Prepare request data
792     *
793     * @return Request
794     */
795    public function prepareData(): Request
796    {
797        if (!$this->data->hasRequest()) {
798            $this->data->setRequest($this);
799        }
800
801        $this->data->prepare();
802
803        return $this;
804    }
805
806    /**
807     * Magic method to check the is[Method](), i.e. $request->isPost();
808     *
809     * @param  string $methodName
810     * @param  ?array $arguments
811     * @throws Exception
812     * @return bool
813     */
814    public function __call(string $methodName, ?array $arguments = null): bool
815    {
816        if (str_starts_with($methodName, 'is')) {
817            $method = strtoupper(substr($methodName, 2));
818            if ($this->isValidMethod($method)) {
819                return ($this->method == $method);
820            } else {
821                throw new Exception('Error: That request method is not valid.');
822            }
823        } else {
824            throw new Exception('Error: That method/function is not valid.');
825        }
826    }
827
828}