ResponseTrait   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 91
c 1
b 0
f 0
dl 0
loc 206
ccs 33
cts 33
cp 1
rs 10
wmc 14

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getStatusCode() 0 3 1
A getReasonPhrase() 0 3 1
A init() 0 11 1
A setStatus() 0 11 4
B withStatus() 0 22 7
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 9
    public function getStatusCode(): int
124
    {
125 9
        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
     * @psalm-suppress NoValue
151
     */
152 32
    public function withStatus($code, $reasonPhrase = ''): ResponseInterface
153
    {
154 32
        if (!is_int($code)) {
0 ignored issues
show
introduced by
The condition is_int($code) is always true.
Loading history...
155 12
            if (!is_numeric($code) || is_float($code)) {
156 11
                throw new InvalidArgumentException(sprintf(
157 11
                    'Response status code is not valid. It must be an integer, %s received.',
158 11
                    (is_object($code) ? get_class($code) : gettype($code))
159 11
                ));
160
            }
161 1
            $code = (int) $code;
162
        }
163
164 21
        if (!is_string($reasonPhrase)) {
0 ignored issues
show
introduced by
The condition is_string($reasonPhrase) is always true.
Loading history...
165 10
            throw new InvalidArgumentException(sprintf(
166 10
                'Response reason phrase is not valid. It must be a string, %s received.',
167 10
                (is_object($reasonPhrase) ? get_class($reasonPhrase) : gettype($reasonPhrase))
168 10
            ));
169
        }
170
171 11
        $new = clone $this;
172 11
        $new->setStatus($code, $reasonPhrase);
173 6
        return $new;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $new returns the type HttpSoft\Message\ResponseTrait which is incompatible with the type-hinted return Psr\Http\Message\ResponseInterface.
Loading history...
174
    }
175
176
    /**
177
     * Gets the response reason phrase associated with the status code.
178
     *
179
     * Because a reason phrase is not a required element in a response
180
     * status line, the reason phrase value MAY be null. Implementations MAY
181
     * choose to return the default RFC 7231 recommended reason phrase (or those
182
     * listed in the IANA HTTP Status Code Registry) for the response's
183
     * status code.
184
     *
185
     * @link http://tools.ietf.org/html/rfc7231#section-6
186
     * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
187
     * @return string Reason phrase; must return an empty string if none present.
188
     */
189 8
    public function getReasonPhrase(): string
190
    {
191 8
        return $this->reasonPhrase;
192
    }
193
194
    /**
195
     * @param int $statusCode
196
     * @param string $reasonPhrase
197
     * @param StreamInterface|string|resource|null $body
198
     * @param array $headers
199
     * @param string $protocol
200
     */
201 74
    private function init(
202
        int $statusCode = 200,
203
        string $reasonPhrase = '',
204
        array $headers = [],
205
        $body = null,
206
        string $protocol = '1.1'
207
    ): void {
208 74
        $this->setStatus($statusCode, $reasonPhrase);
209 74
        $this->registerStream($body);
210 74
        $this->registerHeaders($headers);
211 74
        $this->registerProtocolVersion($protocol);
212
    }
213
214
    /**
215
     * @param int $statusCode
216
     * @param string $reasonPhrase
217
     * @throws InvalidArgumentException for invalid status code arguments.
218
     * @link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
219
     */
220 74
    private function setStatus(int $statusCode, string $reasonPhrase = ''): void
221
    {
222 74
        if ($statusCode < 100 || $statusCode > 599) {
223 5
            throw new InvalidArgumentException(sprintf(
224 5
                'Response status code "%d" is not valid. It must be in 100..599 range.',
225 5
                $statusCode
226 5
            ));
227
        }
228
229 74
        $this->statusCode = $statusCode;
230 74
        $this->reasonPhrase = $reasonPhrase ?: (self::$phrases[$statusCode] ?? '');
231
    }
232
}
233