Passed
Push — develop ( 40a09f...894dbc )
by nguereza
02:40
created

Response::getStatusCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
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
class Response extends Message implements ResponseInterface
53
{
54
    /**
55
     * The reason phrases.
56
     */
57
    protected const REASON_PHRASES = [
58
        //
59
        // Informational
60
        //
61
        100 => 'Continue',
62
        101 => 'Switching Protocols',
63
        102 => 'Processing',
64
        103 => 'Early Hints',
65
        //
66
        // Successful
67
        //
68
        200 => 'OK',
69
        201 => 'Created',
70
        202 => 'Accepted',
71
        203 => 'Non-Authoritative Information',
72
        204 => 'No Content',
73
        205 => 'Reset Content',
74
        206 => 'Partial Content',
75
        207 => 'Multi-Status',
76
        208 => 'Already Reported',
77
        226 => 'IM Used',
78
        //
79
        // Redirection
80
        //
81
        300 => 'Multiple Choices',
82
        301 => 'Moved Permanently',
83
        302 => 'Found',
84
        303 => 'See Other',
85
        304 => 'Not Modified',
86
        305 => 'Use Proxy',
87
        307 => 'Temporary Redirect',
88
        308 => 'Permanent Redirect',
89
        //
90
        // Client Error
91
        //
92
        400 => 'Bad Request',
93
        401 => 'Unauthorized',
94
        402 => 'Payment Required',
95
        403 => 'Forbidden',
96
        404 => 'Not Found',
97
        405 => 'Method Not Allowed',
98
        406 => 'Not Acceptable',
99
        407 => 'Proxy Authentication Required',
100
        408 => 'Request Timeout',
101
        409 => 'Conflict',
102
        410 => 'Gone',
103
        411 => 'Length Required',
104
        412 => 'Precondition Failed',
105
        413 => 'Payload Too Large',
106
        414 => 'URI Too Long',
107
        415 => 'Unsupported Media Type',
108
        416 => 'Range Not Satisfiable',
109
        417 => 'Expectation Failed',
110
        421 => 'Misdirected Request',
111
        422 => 'Unprocessable Entity',
112
        423 => 'Locked',
113
        424 => 'Failed Dependency',
114
        425 => 'Too Early',
115
        426 => 'Upgrade Required',
116
        428 => 'Precondition Required',
117
        429 => 'Too Many Requests',
118
        431 => 'Request Header Fields Too Large',
119
        451 => 'Unavailable For Legal Reasons',
120
        //
121
        // Server Error
122
        //
123
        500 => 'Internal Server Error',
124
        501 => 'Not Implemented',
125
        502 => 'Bad Gateway',
126
        503 => 'Service Unavailable',
127
        504 => 'Gateway Timeout',
128
        505 => 'HTTP Version Not Supported',
129
        506 => 'Variant Also Negotiates',
130
        507 => 'Insufficient Storage',
131
        508 => 'Loop Detected',
132
        510 => 'Not Extended',
133
        511 => 'Network Authentication Required'
134
    ];
135
136
    /**
137
     * The Response HTTP status code
138
     * @var int
139
     */
140
    protected int $statusCode = 200;
141
142
    /**
143
     * The Reason phrase
144
     * @var string
145
     */
146
    protected string $reasonPhrase = 'OK';
147
148
    /**
149
     * Create new Response instance
150
     * @param int $statusCode
151
     * @param string      $reasonPhrase
152
     */
153
    public function __construct(int $statusCode = 200, string $reasonPhrase = '')
154
    {
155
        $this->statusCode = $this->filterStatusCode($statusCode);
156
        if ($reasonPhrase === '') {
157
            $reasonPhrase = isset(static::REASON_PHRASES[$this->statusCode])
158
                    ? static::REASON_PHRASES[$this->statusCode]
159
                    : '';
160
        }
161
        $this->reasonPhrase = $reasonPhrase;
162
163
        // Set common headers
164
        $this->setCommonHeaders();
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 = isset(static::REASON_PHRASES[$that->statusCode])
185
                    ? static::REASON_PHRASES[$that->statusCode]
186
                    : '';
187
        }
188
        $that->reasonPhrase = $reasonPhrase;
189
190
        return $that;
191
    }
192
193
    /**
194
     * {@inheritdoc}
195
     */
196
    public function getReasonPhrase(): string
197
    {
198
        return $this->reasonPhrase;
199
    }
200
201
    /**
202
     * The string representation of this response
203
     * @return string
204
     */
205
    public function __toString(): string
206
    {
207
        $response = sprintf(
208
            "HTTP/%s %d %s\r\n",
209
            $this->getProtocolVersion(),
210
            $this->getStatusCode(),
211
            $this->getReasonPhrase()
212
        );
213
214
        foreach (array_keys($this->headers) as $header) {
215
            if (strtolower($header) === 'set-cookie') {
216
                foreach ($this->getHeader('Set-Cookie') as $setCookie) {
217
                    $response .= sprintf(
218
                        "%s: %s\r\n",
219
                        $header,
220
                        $setCookie
221
                    );
222
                }
223
            } else {
224
                $response .= sprintf(
225
                    "%s: %s\r\n",
226
                    $header,
227
                    $this->getHeaderLine($header)
228
                );
229
            }
230
        }
231
        return sprintf(
232
            "%s\r\n%s",
233
            $response,
234
            $this->getBody()
235
        );
236
    }
237
238
    /**
239
     * Filter status code
240
     * @param  int    $code
241
     * @return int
242
     */
243
    protected function filterStatusCode(int $code): int
244
    {
245
        if ($code === 306) {
246
            throw new InvalidArgumentException(
247
                'Invalid status code! Status code 306 is unused.'
248
            );
249
        }
250
251
        if ($code < 100 || $code > 599) {
252
            throw new InvalidArgumentException(
253
                'Invalid status code! Status code must be between 100 and 599.'
254
            );
255
        }
256
257
        return $code;
258
    }
259
260
    /**
261
     * Set the common headers to be used
262
     * @return $this
263
     */
264
    protected function setCommonHeaders(): self
265
    {
266
        $this->withAddedHeader(
267
            'Content-Security-Policy',
268
            'default-src \'self\'; frame-ancestors \'self\'; form-action \'self\';'
269
        );
270
271
        $this->withAddedHeader('X-Content-Type-Options', 'nosniff');
272
273
        return $this;
274
    }
275
}
276