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