Passed
Push — master ( e41252...69cd60 )
by Evgeniy
01:59
created

src/ResponseTrait.php (3 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace HttpSoft\Message;
6
7
use InvalidArgumentException;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\StreamInterface;
10
11
use function gettype;
12
use function get_class;
13
use function is_int;
14
use function is_float;
15
use function is_numeric;
16
use function is_object;
17
use function is_string;
18
use function sprintf;
19
20
/**
21
 * Trait implementing the methods defined in `Psr\Http\Message\ResponseInterface`.
22
 *
23
 * @see https://github.com/php-fig/http-message/tree/master/src/ResponseInterface.php
24
 */
25
trait ResponseTrait
26
{
27
    use MessageTrait;
28
29
    /**
30
     * Map of standard HTTP status code and reason phrases.
31
     *
32
     * @link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
33
     * @var array<int, string>
34
     */
35
    private static array $phrases = [
36
        // Informational 1xx
37
        100 => 'Continue',
38
        101 => 'Switching Protocols',
39
        102 => 'Processing',
40
        103 => 'Early Hints',
41
        // Successful 2xx
42
        200 => 'OK',
43
        201 => 'Created',
44
        202 => 'Accepted',
45
        203 => 'Non-Authoritative Information',
46
        204 => 'No Content',
47
        205 => 'Reset Content',
48
        206 => 'Partial Content',
49
        207 => 'Multi-Status',
50
        208 => 'Already Reported',
51
        226 => 'IM Used',
52
        // Redirection 3xx
53
        300 => 'Multiple Choices',
54
        301 => 'Moved Permanently',
55
        302 => 'Found',
56
        303 => 'See Other',
57
        304 => 'Not Modified',
58
        305 => 'Use Proxy',
59
        307 => 'Temporary Redirect',
60
        308 => 'Permanent Redirect',
61
        // Client Errors 4xx
62
        400 => 'Bad Request',
63
        401 => 'Unauthorized',
64
        402 => 'Payment Required',
65
        403 => 'Forbidden',
66
        404 => 'Not Found',
67
        405 => 'Method Not Allowed',
68
        406 => 'Not Acceptable',
69
        407 => 'Proxy Authentication Required',
70
        408 => 'Request Timeout',
71
        409 => 'Conflict',
72
        410 => 'Gone',
73
        411 => 'Length Required',
74
        412 => 'Precondition Failed',
75
        413 => 'Payload Too Large',
76
        414 => 'URI Too Long',
77
        415 => 'Unsupported Media Type',
78
        416 => 'Range Not Satisfiable',
79
        417 => 'Expectation Failed',
80
        418 => 'I\'m a teapot',
81
        421 => 'Misdirected Request',
82
        422 => 'Unprocessable Entity',
83
        423 => 'Locked',
84
        424 => 'Failed Dependency',
85
        425 => 'Too Early',
86
        426 => 'Upgrade Required',
87
        428 => 'Precondition Required',
88
        429 => 'Too Many Requests',
89
        431 => 'Request Header Fields Too Large',
90
        451 => 'Unavailable For Legal Reasons',
91
        // Server Errors 5xx
92
        500 => 'Internal Server Error',
93
        501 => 'Not Implemented',
94
        502 => 'Bad Gateway',
95
        503 => 'Service Unavailable',
96
        504 => 'Gateway Timeout',
97
        505 => 'HTTP Version Not Supported',
98
        506 => 'Variant Also Negotiates',
99
        507 => 'Insufficient Storage',
100
        508 => 'Loop Detected',
101
        510 => 'Not Extended',
102
        511 => 'Network Authentication Required',
103
    ];
104
105
    /**
106
     * @var int
107
     */
108
    private int $statusCode;
109
110
    /**
111
     * @var string
112
     */
113
    private string $reasonPhrase;
114
115
    /**
116
     * Gets the response status code.
117
     *
118
     * The status code is a 3-digit integer result code of the server's attempt
119
     * to understand and satisfy the request.
120
     *
121
     * @return int Status code.
122
     */
123 6
    public function getStatusCode(): int
124
    {
125 6
        return $this->statusCode;
126
    }
127
128
    /**
129
     * Return an instance with the specified status code and, optionally, reason phrase.
130
     *
131
     * If no reason phrase is specified, implementations MAY choose to default
132
     * to the RFC 7231 or IANA recommended reason phrase for the response's
133
     * status code.
134
     *
135
     * This method MUST be implemented in such a way as to retain the
136
     * immutability of the message, and MUST return an instance that has the
137
     * updated status and reason phrase.
138
     *
139
     * @link http://tools.ietf.org/html/rfc7231#section-6
140
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
141
     * @param int $code The 3-digit integer result code to set.
142
     * @param string $reasonPhrase The reason phrase to use with the
143
     *     provided status code; if none is provided, implementations MAY
144
     *     use the defaults as suggested in the HTTP specification.
145
     * @return static
146
     * @throws InvalidArgumentException for invalid status code arguments.
147
     * @psalm-suppress DocblockTypeContradiction
148
     * @psalm-suppress TypeDoesNotContainType
149
     * @psalm-suppress RedundantCondition
150
     */
151 24
    public function withStatus($code, $reasonPhrase = ''): ResponseInterface
152
    {
153 24
        if (!is_int($code)) {
0 ignored issues
show
The condition is_int($code) is always true.
Loading history...
154 8
            if (!is_numeric($code) || is_float($code)) {
155 8
                throw new InvalidArgumentException(sprintf(
156
                    'Response status code is not valid. Must be a integer, received `%s`.',
157 8
                    (is_object($code) ? get_class($code) : gettype($code))
158
                ));
159
            }
160
            $code = (int) $code;
161
        }
162
163 16
        if (!is_string($reasonPhrase)) {
0 ignored issues
show
The condition is_string($reasonPhrase) is always true.
Loading history...
164 10
            throw new InvalidArgumentException(sprintf(
165
                'Response reason phrase is not valid. Must be a string, received `%s`.',
166 10
                (is_object($reasonPhrase) ? get_class($reasonPhrase) : gettype($reasonPhrase))
167
            ));
168
        }
169
170 6
        $new = clone $this;
171 6
        $new->setStatus($code, $reasonPhrase);
172 3
        return $new;
0 ignored issues
show
Bug Best Practice introduced by devanych
The expression return $new returns the type HttpSoft\Message\ResponseTrait which is incompatible with the type-hinted return Psr\Http\Message\ResponseInterface.
Loading history...
173
    }
174
175
    /**
176
     * Gets the response reason phrase associated with the status code.
177
     *
178
     * Because a reason phrase is not a required element in a response
179
     * status line, the reason phrase value MAY be null. Implementations MAY
180
     * choose to return the default RFC 7231 recommended reason phrase (or those
181
     * listed in the IANA HTTP Status Code Registry) for the response's
182
     * status code.
183
     *
184
     * @link http://tools.ietf.org/html/rfc7231#section-6
185
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
186
     * @return string Reason phrase; must return an empty string if none present.
187
     * @psalm-suppress RedundantCondition
188
     */
189 6
    public function getReasonPhrase(): string
190
    {
191 6
        return $this->reasonPhrase;
192
    }
193
194
    /**
195
     * @param int $statusCode
196
     * @param string $reasonPhrase
197
     * @param StreamInterface|string|resource $body
198
     * @param array $headers
199
     * @param string $protocol
200
     * @psalm-suppress MixedArgumentTypeCoercion
201
     */
202 27
    private function init(
203
        int $statusCode = 200,
204
        string $reasonPhrase = '',
205
        array $headers = [],
206
        $body = 'php://temp',
207
        string $protocol = '1.1'
208
    ): void {
209 27
        $this->setStatus($statusCode, $reasonPhrase);
210 27
        $this->registerStream($body);
211 27
        $this->registerHeaders($headers);
212 27
        $this->registerProtocolVersion($protocol);
213 27
    }
214
215
    /**
216
     * @param int $statusCode
217
     * @param string $reasonPhrase
218
     * @throws InvalidArgumentException for invalid status code arguments.
219
     * @link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
220
     */
221 27
    private function setStatus(int $statusCode, string $reasonPhrase = ''): void
222
    {
223 27
        if ($statusCode < 100 || $statusCode > 599) {
224 3
            throw new InvalidArgumentException(sprintf(
225
                'Response status code `%d` is not valid. Must be in the range from 100 to 599 enabled.',
226 3
                $statusCode
227
            ));
228
        }
229
230 27
        $this->statusCode = $statusCode;
231 27
        $this->reasonPhrase = $reasonPhrase ?: (self::$phrases[$statusCode] ?? '');
232 27
    }
233
}
234