Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
61 / 61
100.00% covered (success)
100.00%
22 / 22
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractRequestResponse
100.00% covered (success)
100.00%
61 / 61
100.00% covered (success)
100.00%
22 / 22
47
100.00% covered (success)
100.00%
1 / 1
 addHeader
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 addHeaders
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHeaderAsString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHeaderValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getHeaderValueAsString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getHeaders
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHeadersAsArray
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
5
 getHeadersAsString
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 hasHeaders
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeHeader
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 removeHeaders
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setBody
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getBody
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBodyContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getBodyContentLength
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 hasBody
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasBodyContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 decodeBodyContent
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 removeBody
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Http;
15
16use Pop\Mime\Part\Header;
17use Pop\Mime\Part\Body;
18
19/**
20 * Abstract HTTP request/response class
21 *
22 * @category   Pop
23 * @package    Pop\Http
24 * @author     Nick Sagona, III <dev@nolainteractive.com>
25 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
26 * @license    http://www.popphp.org/license     New BSD License
27 * @version    5.0.0
28 */
29abstract class AbstractRequestResponse implements RequestResponseInterface
30{
31
32    /**
33     * Headers
34     * @var array
35     */
36    protected array $headers = [];
37
38    /**
39     * Body
40     * @var ?Body
41     */
42    protected ?Body $body = null;
43
44    /**
45     * Add a header
46     *
47     * @param  Header|string|int $header
48     * @param  ?string           $value
49     * @return AbstractRequestResponse
50     */
51    public function addHeader(Header|string|int $header, ?string $value = null): AbstractRequestResponse
52    {
53        if ($header instanceof Header) {
54            $this->headers[$header->getName()] = $header;
55        } else {
56            if (is_numeric($header) && str_contains($value, ':')) {
57                $header = Header::parse($value);
58                $this->headers[$header->getName()] = $header;
59            } else {
60                $this->headers[$header] = new Header($header, $value);
61            }
62        }
63
64        return $this;
65    }
66
67    /**
68     * Add all headers
69     *
70     * @param  array $headers
71     * @return AbstractRequestResponse
72     */
73    public function addHeaders(array $headers): AbstractRequestResponse
74    {
75        foreach ($headers as $header => $value) {
76            if ($value instanceof Header) {
77                $this->addHeader($value);
78            } else {
79                $this->addHeader($header, $value);
80            }
81        }
82        return $this;
83    }
84
85    /**
86     * Get a header
87     *
88     * @param  string $name
89     * @return mixed
90     */
91    public function getHeader(string $name): mixed
92    {
93        return $this->headers[$name] ?? null;
94    }
95
96    /**
97     * Get a header
98     *
99     * @param  string $name
100     * @return mixed
101     */
102    public function getHeaderAsString(string $name): mixed
103    {
104        return (string)$this->headers[$name] ?? null;
105    }
106
107    /**
108     * Get header value
109     *
110     * @param  string $name
111     * @param  int    $i
112     * @return mixed
113     */
114    public function getHeaderValue(string $name, int $i = 0): mixed
115    {
116        return (isset($this->headers[$name])) ? $this->headers[$name]?->getValue($i) : null;
117    }
118
119    /**
120     * Get header value as string
121     *
122     * @param  string $name
123     * @param  int    $i
124     * @return string|null
125     */
126    public function getHeaderValueAsString(string $name, int $i = 0): string|null
127    {
128        return (isset($this->headers[$name])) ? $this->headers[$name]?->getValueAsString($i) : null;
129    }
130
131    /**
132     * Get all headers
133     *
134     * @return array
135     */
136    public function getHeaders(): array
137    {
138        return $this->headers;
139    }
140
141    /**
142     * Get all header values as associative array
143     *
144     * @param  bool $asStrings
145     * @return array
146     */
147    public function getHeadersAsArray(bool $asStrings = true): array
148    {
149        $headers = [];
150
151        foreach ($this->headers as $name => $header) {
152            if (count($header->getValues()) == 1) {
153                $headers[$name] = ($asStrings) ? $header->getValueAsString(0) : $header->getValue(0);
154            } else {
155                $headers[$name] = ($asStrings) ? $header->getValuesAsStrings() : $header->getValues();
156            }
157        }
158        return $headers;
159    }
160
161    /**
162     * Get all header values formatted string
163     *
164     * @param  mixed  $status
165     * @param  string $eol
166     * @return string
167     */
168    public function getHeadersAsString(mixed $status = null, string $eol = "\r\n"): string
169    {
170        $headers = '';
171
172        if (is_string($status)) {
173            $headers = $status . $eol;
174        }
175
176        foreach ($this->headers as $header) {
177            $headers .= $header . $eol;
178        }
179
180        return $headers;
181    }
182
183    /**
184     * Determine if there are headers
185     *
186     * @return bool
187     */
188    public function hasHeaders(): bool
189    {
190        return (count($this->headers) > 0);
191    }
192
193    /**
194     * Has a header
195     *
196     * @param  string $name
197     * @return bool
198     */
199    public function hasHeader(string $name): bool
200    {
201        return (isset($this->headers[$name]));
202    }
203
204    /**
205     * Remove a header
206     *
207     * @param  string $name
208     * @return AbstractRequestResponse
209     */
210    public function removeHeader(string $name): AbstractRequestResponse
211    {
212        if (isset($this->headers[$name])) {
213            unset($this->headers[$name]);
214        }
215
216        return $this;
217    }
218
219    /**
220     * Remove all headers
221     *
222     * @return AbstractRequestResponse
223     */
224    public function removeHeaders(): AbstractRequestResponse
225    {
226        $this->headers = [];
227        return $this;
228    }
229
230    /**
231     * Set the body
232     *
233     * @param  string|Body $body
234     * @return AbstractRequestResponse
235     */
236    public function setBody(string|Body $body): AbstractRequestResponse
237    {
238        $this->body = ($body instanceof Body) ? $body : new Body($body);
239        return $this;
240    }
241
242    /**
243     * Get the body
244     *
245     * @return Body
246     */
247    public function getBody(): Body
248    {
249        return $this->body;
250    }
251
252    /**
253     * Get body content
254     *
255     * @return mixed
256     */
257    public function getBodyContent(): mixed
258    {
259        return ($this->body !== null) ? $this->body->getContent() : null;
260    }
261
262    /**
263     * Get body content length
264     *
265     * @param  bool $mb
266     * @return int
267     */
268    public function getBodyContentLength(bool $mb = false): int
269    {
270        if ($this->body !== null) {
271            return ($mb) ? mb_strlen($this->body->getContent()) : strlen($this->body->getContent());
272        } else {
273            return 0;
274        }
275    }
276
277    /**
278     * Has a body
279     *
280     * @return bool
281     */
282    public function hasBody(): bool
283    {
284        return ($this->body !== null);
285    }
286
287    /**
288     * Has body content
289     *
290     * @return bool
291     */
292    public function hasBodyContent(): bool
293    {
294        return (($this->body !== null) && $this->body->hasContent());
295    }
296
297    /**
298     * Decode the body
299     *
300     * @param  ?string $body
301     * @return Body
302     */
303    public function decodeBodyContent(?string $body = null): body
304    {
305        if ($body !== null) {
306            $this->setBody($body);
307        }
308        if (($this->hasHeader('Transfer-Encoding')) && (count($this->getHeader('Transfer-Encoding')->getValues()) == 1) &&
309            (strtolower($this->getHeader('Transfer-Encoding')->getValue(0)) == 'chunked')) {
310            $this->body->setContent(Parser::decodeChunkedData($this->body->getContent()));
311        }
312        $contentEncoding = ($this->hasHeader('Content-Encoding') && (count($this->getHeader('Content-Encoding')->getValues()) == 1)) ?
313            $this->getHeader('Content-Encoding')->getValue(0) : null;
314        $this->body->setContent(Parser::decodeData($this->body->getContent(), $contentEncoding));
315
316        return $this->body;
317    }
318
319    /**
320     * Remove the body
321     *
322     * @return AbstractRequestResponse
323     */
324    public function removeBody(): AbstractRequestResponse
325    {
326        $this->body = null;
327        return $this;
328    }
329
330    /**
331     * Magic method to get either the headers or body
332     *
333     * @param  string $name
334     * @return mixed
335     */
336    public function __get(string $name): mixed
337    {
338        return match ($name) {
339            'headers' => $this->headers,
340            'body'    => $this->body,
341            default   => null,
342        };
343    }
344
345}