Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.00% covered (success)
80.00%
40 / 50
50.00% covered (warning)
50.00%
6 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Response
80.00% covered (success)
80.00%
40 / 50
50.00% covered (warning)
50.00%
6 / 12
32.83
0.00% covered (danger)
0.00%
0 / 1
 prepareBody
87.50% covered (success)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
7.10
 getHeadersAsString
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 sendHeaders
71.43% covered (success)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
4.37
 send
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 sendAndExit
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 render
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 redirect
83.33% covered (success)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 redirectAndExit
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 forward
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 forwardAndExit
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getMessageFromCode
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
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\Server;
16
17use Pop\Http\Parser;
18use Pop\Http\AbstractResponse;
19use Pop\Http\Client;
20
21/**
22 * HTTP server 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 */
31class Response extends AbstractResponse
32{
33
34    /**
35     * Prepare response body
36     *
37     * @param  bool $length
38     * @param  bool $mb
39     * @return string
40     */
41    public function prepareBody(bool $length = false, bool $mb = false): string
42    {
43        $body = $this->body->render();
44
45        if ($this->hasHeader('Content-Encoding') && (count($this->getHeaderObject('Content-Encoding')->getValues()) == 1)) {
46            $body = Parser::encodeData($body, strtoupper((string)$this->getHeaderObject('Content-Encoding')->getValueAsString(0)));
47            if ($length) {
48                $this->addHeader('Content-Length', (string)(($mb) ? mb_strlen($body) : strlen($body)));
49            }
50        } else if ($length) {
51            $this->addHeader('Content-Length', (string)(($mb) ? mb_strlen($body) : strlen($body)));
52        }
53
54        return $body;
55    }
56
57    /**
58     * Get the response headers as a string
59     *
60     * @param  mixed  $status
61     * @param  string $eol
62     * @return string
63     */
64    public function getHeadersAsString(mixed $status = null, string $eol = "\r\n"): string
65    {
66        $httpStatus = ($status === true) ? "HTTP/{$this->version} {$this->code} {$this->message}" : $status;
67        return parent::getHeadersAsString($httpStatus, $eol);
68    }
69
70    /**
71     * Send response headers
72     *
73     * @throws Exception
74     * @return void
75     */
76    public function sendHeaders(): void
77    {
78        if (headers_sent()) {
79            throw new Exception('The headers have already been sent.');
80        }
81
82        header("HTTP/{$this->version} {$this->code} {$this->message}");
83        foreach ($this->headers as $name => $value) {
84            if ($value instanceof \Pop\Mime\Part\Header) {
85                header((string)$value);
86            } else {
87                header($name . ": " . $value);
88            }
89        }
90    }
91
92    /**
93     * Send full response
94     *
95     * @param  ?int   $code
96     * @param  ?array $headers
97     * @param  bool   $length
98     * @throws Exception|\Pop\Http\Exception
99     * @return void
100     */
101    public function send(?int $code = null, ?array $headers = null, bool $length = false): void
102    {
103        if ($code !== null) {
104            $this->setCode($code);
105        }
106        if ($headers !== null) {
107            $this->addHeaders($headers);
108        }
109
110        $body = $this->prepareBody($length);
111
112        $this->sendHeaders();
113        echo $body;
114    }
115
116    /**
117     * Send full response and exit
118     *
119     * @param  ?int   $code
120     * @param  ?array $headers
121     * @param  bool   $length
122     * @throws Exception|\Pop\Http\Exception
123     * @return void
124     */
125    public function sendAndExit(?int $code = null, ?array $headers = null, $length = false)
126    {
127        $this->send($code, $headers, $length);
128        exit();
129    }
130
131    /**
132     * Render entire response as a string
133     *
134     * @return string
135     */
136    public function render(): string
137    {
138        $body = $this->prepareBody();
139        return $this->getHeadersAsString(true) . "\r\n" . $body;
140    }
141
142    /**
143     * Return entire response as a string
144     *
145     * @return string
146     */
147    public function __toString(): string
148    {
149        return $this->render();
150    }
151
152    /**
153     * Send redirect
154     *
155     * @param  string $url
156     * @param  int    $code
157     * @param  string $version
158     * @throws Exception
159     * @return void
160     */
161    public static function redirect(string $url, int $code = 302, string $version = '1.1'): void
162    {
163        if (headers_sent()) {
164            throw new Exception('The headers have already been sent.');
165        }
166
167        if (!array_key_exists($code, self::$responseCodes)) {
168            throw new Exception('The header code '. $code . ' is not allowed.');
169        }
170
171        header("HTTP/{$version} {$code} " . self::$responseCodes[$code]);
172        header("Location: {$url}");
173    }
174
175    /**
176     * Send redirect and exit
177     *
178     * @param  string $url
179     * @param  int    $code
180     * @param  string $version
181     * @throws Exception
182     * @return void
183     */
184    public static function redirectAndExit(string $url, int $code = 302, string $version = '1.1'): void
185    {
186        static::redirect($url, $code, $version);
187        exit();
188    }
189
190    /**
191     * Forward a client response as the server response
192     *
193     * @param  Client\Response $clientResponse
194     * @throws Exception|\Pop\Http\Exception
195     * @return void
196     */
197    public static function forward(Client\Response $clientResponse): void
198    {
199        $serverResponse = new static([
200            'version' => $clientResponse->getVersion(),
201            'code'    => $clientResponse->getCode(),
202            'message' => $clientResponse->getMessage(),
203            'headers' => $clientResponse->getHeaderObjects(),
204            'body'    => $clientResponse->getBody()
205        ]);
206        $serverResponse->send();
207    }
208
209    /**
210     * Forward a client response as the server response and exit
211     *
212     * @param  Client\Response $clientResponse
213     * @throws Exception|\Pop\Http\Exception
214     * @return void
215     */
216    public static function forwardAndExit(Client\Response $clientResponse): void
217    {
218        static::forward($clientResponse);
219        exit();
220    }
221
222    /**
223     * Get response message from code
224     *
225     * @param  int $code
226     * @throws Exception
227     * @return string
228     */
229    public static function getMessageFromCode(int $code): string
230    {
231        if (!array_key_exists($code, self::$responseCodes)) {
232            throw new Exception('The header code ' . $code . ' is not valid.');
233        }
234
235        return self::$responseCodes[$code];
236    }
237
238}