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