Response::__toString()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 21
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 30
rs 9.584
1
<?php
2
3
/**
4
 * Platine HTTP
5
 *
6
 * Platine HTTP Message is the implementation of PSR 7
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine HTTP
11
 * Copyright (c) 2019 Dion Chaika
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file Response.php
34
 *
35
 *  The Response class is a representation of an outgoing, server-side response.
36
 *
37
 *  @package    Platine\Http
38
 *  @author Platine Developers Team
39
 *  @copyright  Copyright (c) 2020
40
 *  @license    http://opensource.org/licenses/MIT  MIT License
41
 *  @link   https://www.platine-php.com
42
 *  @version 1.0.0
43
 *  @filesource
44
 */
45
46
declare(strict_types=1);
47
48
namespace Platine\Http;
49
50
use InvalidArgumentException;
51
52
/**
53
 * @class Response
54
 * @package Platine\Http
55
 */
56
class Response extends Message implements ResponseInterface
57
{
58
    /**
59
     * The reason phrases.
60
     */
61
    protected const REASON_PHRASES = [
62
        //
63
        // Informational
64
        //
65
        100 => 'Continue',
66
        101 => 'Switching Protocols',
67
        102 => 'Processing',
68
        103 => 'Early Hints',
69
        //
70
        // Successful
71
        //
72
        200 => 'OK',
73
        201 => 'Created',
74
        202 => 'Accepted',
75
        203 => 'Non-Authoritative Information',
76
        204 => 'No Content',
77
        205 => 'Reset Content',
78
        206 => 'Partial Content',
79
        207 => 'Multi-Status',
80
        208 => 'Already Reported',
81
        226 => 'IM Used',
82
        //
83
        // Redirection
84
        //
85
        300 => 'Multiple Choices',
86
        301 => 'Moved Permanently',
87
        302 => 'Found',
88
        303 => 'See Other',
89
        304 => 'Not Modified',
90
        305 => 'Use Proxy',
91
        306 => 'Switch Proxy',
92
        307 => 'Temporary Redirect',
93
        308 => 'Permanent Redirect',
94
        //
95
        // Client Error
96
        //
97
        400 => 'Bad Request',
98
        401 => 'Unauthorized',
99
        402 => 'Payment Required',
100
        403 => 'Forbidden',
101
        404 => 'Not Found',
102
        405 => 'Method Not Allowed',
103
        406 => 'Not Acceptable',
104
        407 => 'Proxy Authentication Required',
105
        408 => 'Request Timeout',
106
        409 => 'Conflict',
107
        410 => 'Gone',
108
        411 => 'Length Required',
109
        412 => 'Precondition Failed',
110
        413 => 'Payload Too Large',
111
        414 => 'URI Too Long',
112
        415 => 'Unsupported Media Type',
113
        416 => 'Range Not Satisfiable',
114
        417 => 'Expectation Failed',
115
        421 => 'Misdirected Request',
116
        422 => 'Unprocessable Entity',
117
        423 => 'Locked',
118
        424 => 'Failed Dependency',
119
        425 => 'Too Early',
120
        426 => 'Upgrade Required',
121
        428 => 'Precondition Required',
122
        429 => 'Too Many Requests',
123
        431 => 'Request Header Fields Too Large',
124
        451 => 'Unavailable For Legal Reasons',
125
        //
126
        // Server Error
127
        //
128
        500 => 'Internal Server Error',
129
        501 => 'Not Implemented',
130
        502 => 'Bad Gateway',
131
        503 => 'Service Unavailable',
132
        504 => 'Gateway Timeout',
133
        505 => 'HTTP Version Not Supported',
134
        506 => 'Variant Also Negotiates',
135
        507 => 'Insufficient Storage',
136
        508 => 'Loop Detected',
137
        510 => 'Not Extended',
138
        511 => 'Network Authentication Required'
139
    ];
140
141
    /**
142
     * The Response HTTP status code
143
     * @var int
144
     */
145
    protected int $statusCode = 200;
146
147
    /**
148
     * The Reason phrase
149
     * @var string
150
     */
151
    protected string $reasonPhrase = 'OK';
152
153
    /**
154
     * Create new Response instance
155
     * @param int $statusCode
156
     * @param string      $reasonPhrase
157
     */
158
    public function __construct(int $statusCode = 200, string $reasonPhrase = '')
159
    {
160
        $this->statusCode = $this->filterStatusCode($statusCode);
161
        if ($reasonPhrase === '') {
162
            $reasonPhrase = static::REASON_PHRASES[$this->statusCode] ?? '';
163
        }
164
        $this->reasonPhrase = $reasonPhrase;
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function getStatusCode(): int
171
    {
172
        return $this->statusCode;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178
    public function withStatus(int $code, string $reasonPhrase = ''): self
179
    {
180
        $that = clone $this;
181
        $that->statusCode = $this->filterStatusCode($code);
182
183
        if ($reasonPhrase === '') {
184
            $reasonPhrase = static::REASON_PHRASES[$that->statusCode] ?? '';
185
        }
186
        $that->reasonPhrase = $reasonPhrase;
187
188
        return $that;
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function getReasonPhrase(): string
195
    {
196
        return $this->reasonPhrase;
197
    }
198
199
    /**
200
     * The string representation of this response
201
     * @return string
202
     */
203
    public function __toString(): string
204
    {
205
        $response = sprintf(
206
            "HTTP/%s %d %s\r\n",
207
            $this->getProtocolVersion(),
208
            $this->getStatusCode(),
209
            $this->getReasonPhrase()
210
        );
211
212
        foreach (array_keys($this->headers) as $header) {
213
            if (strtolower($header) === 'set-cookie') {
214
                foreach ($this->getHeader('Set-Cookie') as $setCookie) {
215
                    $response .= sprintf(
216
                        "%s: %s\r\n",
217
                        $header,
218
                        $setCookie
219
                    );
220
                }
221
            } else {
222
                $response .= sprintf(
223
                    "%s: %s\r\n",
224
                    $header,
225
                    $this->getHeaderLine($header)
226
                );
227
            }
228
        }
229
        return sprintf(
230
            "%s\r\n%s",
231
            $response,
232
            $this->getBody()
233
        );
234
    }
235
236
    /**
237
     * Filter status code
238
     * @param  int    $code
239
     * @return int
240
     */
241
    protected function filterStatusCode(int $code): int
242
    {
243
        if ($code === 306) {
244
            throw new InvalidArgumentException(
245
                'Invalid status code! Status code 306 is unused.'
246
            );
247
        }
248
249
        if ($code < 100 || $code > 599) {
250
            throw new InvalidArgumentException(
251
                'Invalid status code! Status code must be between 100 and 599.'
252
            );
253
        }
254
255
        return $code;
256
    }
257
}
258