Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
43 / 43
CRAP
100.00% covered (success)
100.00%
1 / 1
AbstractResponse
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
43 / 43
54
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
7
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setVersion
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCode
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getCode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasCode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMessage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasMessage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getProtocolVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withProtocolVersion
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getStatusCode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getReasonPhrase
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 withStatus
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 isSuccess
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 isContinue
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isOk
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCreated
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isAccepted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isNoContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRedirect
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isMovedPermanently
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isFound
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 isClientError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isBadRequest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isUnauthorized
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isForbidden
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isNotFound
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isMethodNotAllowed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isNotAcceptable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isRequestTimeout
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isConflict
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isLengthRequired
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isUnsupportedMediaType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isUnprocessableEntity
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isTooManyRequests
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isServerError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isInternalServerError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isBadGateway
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isServiceUnavailable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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;
16
17use Pop\Mime\Part\Header;
18use Pop\Http\Body;
19use Psr\Http\Message\ResponseInterface;
20
21/**
22 * Abstract HTTP response class
23 *
24 * @category   Pop
25 * @package    Pop\Http
26 * @author     Nick Sagona, III <nick@popphp.org>
27 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
28 * @license    https://www.popphp.org/license     New BSD License
29 * @version    6.0.0
30 */
31abstract class AbstractResponse extends AbstractRequestResponse implements ResponseInterface
32{
33
34    /**
35     * Response codes & messages
36     * @var array
37     */
38    protected static array $responseCodes = [
39        // Informational 1xx
40        100 => 'Continue',
41        101 => 'Switching Protocols',
42        102 => 'Processing',
43
44        // Success 2xx
45        200 => 'OK',
46        201 => 'Created',
47        202 => 'Accepted',
48        203 => 'Non-Authoritative Information',
49        204 => 'No Content',
50        205 => 'Reset Content',
51        206 => 'Partial Content',
52        207 => 'Multi-Status',
53        208 => 'Already Reported',
54        226 => 'IM Used',
55
56        // Redirection 3xx
57        300 => 'Multiple Choices',
58        301 => 'Moved Permanently',
59        302 => 'Found',
60        303 => 'See Other',
61        304 => 'Not Modified',
62        305 => 'Use Proxy',
63        306 => 'Switch Proxy',
64        307 => 'Temporary Redirect',
65        308 => 'Permanent Redirect',
66
67        // Client Error 4xx
68        400 => 'Bad Request',
69        401 => 'Unauthorized',
70        402 => 'Payment Required',
71        403 => 'Forbidden',
72        404 => 'Not Found',
73        405 => 'Method Not Allowed',
74        406 => 'Not Acceptable',
75        407 => 'Proxy Authentication Required',
76        408 => 'Request Timeout',
77        409 => 'Conflict',
78        410 => 'Gone',
79        411 => 'Length Required',
80        412 => 'Precondition Failed',
81        413 => 'Payload Too Large',
82        414 => 'URI Too Long',
83        415 => 'Unsupported Media Type',
84        416 => 'Range Not Satisfiable',
85        417 => 'Expectation Failed',
86        421 => 'Misdirected Request',
87        422 => 'Unprocessable Entity',
88        423 => 'Locked',
89        424 => 'Failed Dependency',
90        426 => 'Upgrade Required',
91        428 => 'Precondition Required',
92        429 => 'Too Many Requests',
93        431 => 'Request Header Fields Too Large',
94        451 => 'Unavailable For Legal Reasons',
95
96        // Server Error 5xx
97        500 => 'Internal Server Error',
98        501 => 'Not Implemented',
99        502 => 'Bad Gateway',
100        503 => 'Service Unavailable',
101        504 => 'Gateway Timeout',
102        505 => 'HTTP Version Not Supported',
103        506 => 'Variant Also Negotiates',
104        507 => 'Insufficient Storage',
105        508 => 'Loop Detected',
106        509 => 'Bandwidth Limit Exceeded',
107        510 => 'Not Extended',
108        511 => 'Network Authentication Required'
109    ];
110
111    /**
112     * HTTP version for response, i.e. 1.0, 1.1, 2.0, etc.
113     * @var string
114     */
115    protected string $version = '1.1';
116
117    /**
118     * Response code
119     * @var ?int
120     */
121    protected ?int $code = null;
122
123    /**
124     * Response message
125     * @var ?string
126     */
127    protected ?string $message = null;
128
129    /**
130     * Constructor
131     *
132     * Instantiate the response object
133     *
134     * @param  array $config
135     */
136    public function __construct(array $config = [])
137    {
138        // Check for config values and set defaults
139        if (!isset($config['version'])) {
140            $config['version'] = '1.1';
141        }
142        if (!isset($config['code'])) {
143            $config['code'] = 200;
144        }
145
146        $this->setVersion($config['version'])
147            ->setCode($config['code']);
148
149        if (!isset($config['message'])) {
150            $config['message'] = self::$responseCodes[$config['code']];
151        }
152        if (!isset($config['headers']) || (!is_array($config['headers']))) {
153            $config['headers'] = ['Content-Type' => 'text/html'];
154        }
155        if (isset($config['body'])) {
156            $this->setBody($config['body']);
157        }
158
159        $this->setMessage($config['message'])
160            ->addHeaders($config['headers']);
161    }
162
163    /**
164     * Factory method to create a Response object
165     *
166     * @param  array $config
167     * @return static
168     */
169    public static function create(array $config = []): static
170    {
171        return new static($config);
172    }
173
174    /**
175     * Set the response version
176     *
177     * @param  float|string $version
178     * @return AbstractResponse
179     */
180    public function setVersion(float|string $version): AbstractResponse
181    {
182        $this->version = (string)$version;
183        return $this;
184    }
185
186    /**
187     * Get the response HTTP version
188     *
189     * @return string
190     */
191    public function getVersion(): string
192    {
193        return $this->version;
194    }
195
196    /**
197     * Has the response HTTP version
198     *
199     * @return bool
200     */
201    public function hasVersion(): bool
202    {
203        return ($this->version !== '');
204    }
205
206    /**
207     * Set the response code
208     *
209     * @param  int $code
210     * @throws Exception
211     * @return AbstractResponse
212     */
213    public function setCode(int $code = 200): AbstractResponse
214    {
215        if (!array_key_exists($code, self::$responseCodes)) {
216            throw new Exception("Error: The header code '" . $code . "' is not allowed.");
217        }
218
219        $this->code    = $code;
220        $this->message = self::$responseCodes[$code];
221
222        return $this;
223    }
224
225    /**
226     * Get the response code
227     *
228     * @return int
229     */
230    public function getCode(): int
231    {
232        return $this->code;
233    }
234
235    /**
236     * Has the response code
237     *
238     * @return bool
239     */
240    public function hasCode(): bool
241    {
242        return ($this->code !== null);
243    }
244
245    /**
246     * Set the response message
247     *
248     * @param  ?string $message
249     * @return AbstractResponse
250     */
251    public function setMessage(?string $message = null): AbstractResponse
252    {
253        $this->message = $message;
254        return $this;
255    }
256
257    /**
258     * Get the response HTTP message
259     *
260     * @return string
261     */
262    public function getMessage(): string
263    {
264        return $this->message;
265    }
266
267    /**
268     * Has the response message
269     *
270     * @return bool
271     */
272    public function hasMessage(): bool
273    {
274        return ($this->message !== null);
275    }
276
277    /**
278     * Get the HTTP protocol version, per PSR-7 (alias of getVersion())
279     *
280     * @return string
281     */
282    public function getProtocolVersion(): string
283    {
284        return $this->version;
285    }
286
287    /**
288     * Return an instance with the specified HTTP protocol version, per PSR-7
289     *
290     * @param  string $version
291     * @return static
292     */
293    public function withProtocolVersion(string $version): static
294    {
295        $clone = clone $this;
296        $clone->setVersion($version);
297        return $clone;
298    }
299
300    /**
301     * Get the response status code, per PSR-7 (alias of getCode())
302     *
303     * @return int
304     */
305    public function getStatusCode(): int
306    {
307        return $this->code;
308    }
309
310    /**
311     * Get the response reason phrase, per PSR-7 (alias of getMessage())
312     *
313     * @return string
314     */
315    public function getReasonPhrase(): string
316    {
317        return $this->message ?? '';
318    }
319
320    /**
321     * Return an instance with the specified status code and reason phrase, per PSR-7
322     *
323     * @param  int    $code
324     * @param  string $reasonPhrase
325     * @return static
326     */
327    public function withStatus(int $code, string $reasonPhrase = ''): static
328    {
329        $clone = clone $this;
330        $clone->setCode($code);
331        if ($reasonPhrase !== '') {
332            $clone->setMessage($reasonPhrase);
333        }
334        return $clone;
335    }
336
337    /**
338     * Determine if the response is a success
339     *
340     * @return bool
341     */
342    public function isSuccess(): bool
343    {
344        $type = floor($this->code / 100);
345        return (($type == 1) || ($type == 2) || ($type == 3));
346    }
347
348    /**
349     * Determine if the response is a 100 continue
350     *
351     * @return bool
352     */
353    public function isContinue(): bool
354    {
355        $type = floor($this->code / 100);
356        return ($type == 1);
357    }
358
359    /**
360     * Determine if the response is 200 OK
361     *
362     * @return bool
363     */
364    public function isOk(): bool
365    {
366        return ($this->code == 200);
367    }
368
369    /**
370     * Determine if the response is 201 created
371     *
372     * @return bool
373     */
374    public function isCreated(): bool
375    {
376        return ($this->code == 201);
377    }
378
379    /**
380     * Determine if the response is 202 accepted
381     *
382     * @return bool
383     */
384    public function isAccepted(): bool
385    {
386        return ($this->code == 202);
387    }
388
389    /**
390     * Determine if the response is 204 No Content
391     *
392     * @return bool
393     */
394    public function isNoContent(): bool
395    {
396        return ($this->code == 204);
397    }
398
399    /**
400     * Determine if the response is a redirect
401     *
402     * @return bool
403     */
404    public function isRedirect(): bool
405    {
406        $type = floor($this->code / 100);
407        return ($type == 3);
408    }
409
410    /**
411     * Determine if the response is a 301 Moved Permanently
412     *
413     * @return bool
414     */
415    public function isMovedPermanently(): bool
416    {
417        return ($this->code == 301);
418    }
419
420    /**
421     * Determine if the response is a 302 Found
422     *
423     * @return bool
424     */
425    public function isFound(): bool
426    {
427        return ($this->code == 302);
428    }
429
430    /**
431     * Determine if the response is an error
432     *
433     * @return bool
434     */
435    public function isError(): bool
436    {
437        $type = floor($this->code / 100);
438        return (($type == 4) || ($type == 5));
439    }
440
441    /**
442     * Determine if the response is a client error
443     *
444     * @return bool
445     */
446    public function isClientError(): bool
447    {
448        $type = floor($this->code / 100);
449        return ($type == 4);
450    }
451
452    /**
453     * Determine if the response is a 400 Bad Request
454     *
455     * @return bool
456     */
457    public function isBadRequest(): bool
458    {
459        return ($this->code == 400);
460    }
461
462    /**
463     * Determine if the response is a 401 Unauthorized
464     *
465     * @return bool
466     */
467    public function isUnauthorized(): bool
468    {
469        return ($this->code == 401);
470    }
471
472    /**
473     * Determine if the response is a 403 Forbidden
474     *
475     * @return bool
476     */
477    public function isForbidden(): bool
478    {
479        return ($this->code == 403);
480    }
481
482    /**
483     * Determine if the response is a 404 Not Found
484     *
485     * @return bool
486     */
487    public function isNotFound(): bool
488    {
489        return ($this->code == 404);
490    }
491
492    /**
493     * Determine if the response is a 405 Method Not Allowed
494     *
495     * @return bool
496     */
497    public function isMethodNotAllowed(): bool
498    {
499        return ($this->code == 405);
500    }
501
502    /**
503     * Determine if the response is a 406 Not Acceptable
504     *
505     * @return bool
506     */
507    public function isNotAcceptable(): bool
508    {
509        return ($this->code == 406);
510    }
511
512    /**
513     * Determine if the response is a 408 Request Timeout
514     *
515     * @return bool
516     */
517    public function isRequestTimeout(): bool
518    {
519        return ($this->code == 408);
520    }
521
522    /**
523     * Determine if the response is a 409 Conflict
524     *
525     * @return bool
526     */
527    public function isConflict(): bool
528    {
529        return ($this->code == 409);
530    }
531
532    /**
533     * Determine if the response is a 411 Length Required
534     *
535     * @return bool
536     */
537    public function isLengthRequired(): bool
538    {
539        return ($this->code == 411);
540    }
541
542    /**
543     * Determine if the response is a 415 Unsupported Media Type
544     *
545     * @return bool
546     */
547    public function isUnsupportedMediaType(): bool
548    {
549        return ($this->code == 415);
550    }
551
552    /**
553     * Determine if the response is a 422 Unprocessable Entity
554     *
555     * @return bool
556     */
557    public function isUnprocessableEntity(): bool
558    {
559        return ($this->code == 422);
560    }
561
562    /**
563     * Determine if the response is a 429 Too Many Requests
564     *
565     * @return bool
566     */
567    public function isTooManyRequests(): bool
568    {
569        return ($this->code == 429);
570    }
571
572    /**
573     * Determine if the response is a server error
574     *
575     * @return bool
576     */
577    public function isServerError(): bool
578    {
579        $type = floor($this->code / 100);
580        return ($type == 5);
581    }
582
583    /**
584     * Determine if the response is a 500 Internal Server Error
585     *
586     * @return bool
587     */
588    public function isInternalServerError(): bool
589    {
590        return ($this->code == 500);
591    }
592
593    /**
594     * Determine if the response is a 502 Bad Gateway
595     *
596     * @return bool
597     */
598    public function isBadGateway(): bool
599    {
600        return ($this->code == 502);
601    }
602
603    /**
604     * Determine if the response is a 503 Service Unavailable
605     *
606     * @return bool
607     */
608    public function isServiceUnavailable(): bool
609    {
610        return ($this->code == 503);
611    }
612
613}