Passed
Push — master ( 62cb71...c30d3b )
by Evgeniy
02:30 queued 52s
created

RequestTrait::withUri()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.0312

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 7
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 14
ccs 7
cts 8
cp 0.875
crap 4.0312
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace HttpSoft\Message;
6
7
use InvalidArgumentException;
8
use Psr\Http\Message\RequestInterface;
9
use Psr\Http\Message\StreamInterface;
10
use Psr\Http\Message\UriInterface;
11
12
use function gettype;
13
use function get_class;
14
use function is_object;
15
use function is_string;
16
use function preg_match;
17
use function sprintf;
18
19
/**
20
 * Trait implementing the methods defined in `Psr\Http\Message\RequestInterface`.
21
 *
22
 * @see https://github.com/php-fig/http-message/tree/master/src/RequestInterface.php
23
 */
24
trait RequestTrait
25
{
26
    use MessageTrait;
27
28
    /**
29
     * @var string
30
     */
31
    private string $method = 'GET';
32
33
    /**
34
     * @var null|string
35
     */
36
    private ?string $requestTarget = null;
37
38
    /**
39
     * @var UriInterface
40
     */
41
    private UriInterface $uri;
42
43
    /**
44
     * Retrieves the message's request target.
45
     *
46
     * Retrieves the message's request-target either as it will appear (for
47
     * clients), as it appeared at request (for servers), or as it was
48
     * specified for the instance (see withRequestTarget()).
49
     *
50
     * In most cases, this will be the origin-form of the composed URI,
51
     * unless a value was provided to the concrete implementation (see
52
     * withRequestTarget() below).
53
     *
54
     * If no URI is available, and no request-target has been specifically
55
     * provided, this method MUST return the string "/".
56
     *
57
     * @return string
58
     */
59 7
    public function getRequestTarget(): string
60
    {
61 7
        if ($this->requestTarget !== null) {
62 3
            return $this->requestTarget;
63
        }
64
65 5
        $target = $this->uri->getPath();
66
67 5
        if ($target && $query = $this->uri->getQuery()) {
68
            $target .= '?' . $query;
69
        }
70
71 5
        return $target ?: '/';
72
    }
73
74
    /**
75
     * Return an instance with the specific request-target.
76
     *
77
     * If the request needs a non-origin-form request-target — e.g., for
78
     * specifying an absolute-form, authority-form, or asterisk-form —
79
     * this method may be used to create an instance with the specified
80
     * request-target, verbatim.
81
     *
82
     * This method MUST be implemented in such a way as to retain the
83
     * immutability of the message, and MUST return an instance that has the
84
     * changed request target.
85
     *
86
     * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
87
     *     request-target forms allowed in request messages)
88
     * @param mixed $requestTarget
89
     * @return static
90
     */
91 15
    public function withRequestTarget($requestTarget): RequestInterface
92
    {
93 15
        if ($requestTarget === $this->requestTarget) {
94 2
            return $this;
0 ignored issues
show
Bug Best Practice introduced by devanych
The expression return $this returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
95
        }
96
97 13
        if (!is_string($requestTarget) || preg_match('/\s/', $requestTarget)) {
98 10
            throw new InvalidArgumentException(sprintf(
99
                '"%s" is not valid request target. Request target must be a string and cannot contain whitespace.',
100 10
                (is_object($requestTarget) ? get_class($requestTarget) : gettype($requestTarget))
101
            ));
102
        }
103
104 3
        $new = clone $this;
105 3
        $new->requestTarget = $requestTarget;
106 3
        return $new;
0 ignored issues
show
Bug Best Practice introduced by devanych
The expression return $new returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
107
    }
108
109
    /**
110
     * Retrieves the HTTP method of the request.
111
     *
112
     * @return string Returns the request method.
113
     */
114 35
    public function getMethod(): string
115
    {
116 35
        return $this->method;
117
    }
118
119
    /**
120
     * Return an instance with the provided HTTP method.
121
     *
122
     * While HTTP method names are typically all uppercase characters, HTTP
123
     * method names are case-sensitive and thus implementations SHOULD NOT
124
     * modify the given string.
125
     *
126
     * This method MUST be implemented in such a way as to retain the
127
     * immutability of the message, and MUST return an instance that has the
128
     * changed request method.
129
     *
130
     * @param string $method Case-sensitive method.
131
     * @return static
132
     * @throws InvalidArgumentException for invalid HTTP methods.
133
     * @psalm-suppress DocblockTypeContradiction
134
     */
135 13
    public function withMethod($method): RequestInterface
136
    {
137 13
        if ($method === $this->method) {
138 2
            return $this;
0 ignored issues
show
Bug Best Practice introduced by devanych
The expression return $this returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
139
        }
140
141 11
        if (!is_string($method)) {
0 ignored issues
show
introduced by devanych
The condition is_string($method) is always true.
Loading history...
142 6
            throw new InvalidArgumentException(sprintf(
143
                'Invalid HTTP method. It must be a string, %s received.',
144 6
                (is_object($method) ? get_class($method) : gettype($method))
145
            ));
146
        }
147
148 5
        $new = clone $this;
149 5
        $new->method = $method;
150 5
        return $new;
0 ignored issues
show
Bug Best Practice introduced by devanych
The expression return $new returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
151
    }
152
153
    /**
154
     * Retrieves the URI instance.
155
     *
156
     * This method MUST return a UriInterface instance.
157
     *
158
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
159
     * @return UriInterface Returns a UriInterface instance
160
     *     representing the URI of the request.
161
     */
162 33
    public function getUri(): UriInterface
163
    {
164 33
        return $this->uri;
165
    }
166
167
    /**
168
     * Returns an instance with the provided URI.
169
     *
170
     * This method MUST update the Host header of the returned request by
171
     * default if the URI contains a host component. If the URI does not
172
     * contain a host component, any pre-existing Host header MUST be carried
173
     * over to the returned request.
174
     *
175
     * You can opt-in to preserving the original state of the Host header by
176
     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
177
     * `true`, this method interacts with the Host header in the following ways:
178
     *
179
     * - If the Host header is missing or empty, and the new URI contains
180
     *   a host component, this method MUST update the Host header in the returned
181
     *   request.
182
     * - If the Host header is missing or empty, and the new URI does not contain a
183
     *   host component, this method MUST NOT update the Host header in the returned
184
     *   request.
185
     * - If a Host header is present and non-empty, this method MUST NOT update
186
     *   the Host header in the returned request.
187
     *
188
     * This method MUST be implemented in such a way as to retain the
189
     * immutability of the message, and MUST return an instance that has the
190
     * new UriInterface instance.
191
     *
192
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
193
     * @param UriInterface $uri New request URI to use.
194
     * @param bool $preserveHost Preserve the original state of the Host header.
195
     * @return static
196
     * @throws InvalidArgumentException for invalid URI.
197
     */
198 8
    public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
199
    {
200 8
        if ($uri === $this->uri) {
201
            return $this;
0 ignored issues
show
Bug Best Practice introduced by devanych
The expression return $this returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
202
        }
203
204 8
        $new = clone $this;
205 8
        $new->uri = $uri;
206
207 8
        if (!$preserveHost || !$this->hasHeader('host')) {
208 8
            $new->updateHostHeaderFromUri();
209
        }
210
211 8
        return $new;
0 ignored issues
show
Bug Best Practice introduced by devanych
The expression return $new returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
212
    }
213
214
    /**
215
     * @param string $method
216
     * @param UriInterface|string $uri
217
     * @param array $headers
218
     * @param StreamInterface|string|resource $body
219
     * @param string $protocol
220
     * @psalm-suppress MixedArgumentTypeCoercion
221
     */
222 148
    private function init(
223
        string $method = 'GET',
224
        $uri = '',
225
        array $headers = [],
226
        $body = 'php://temp',
227
        string $protocol = '1.1'
228
    ): void {
229 148
        $this->method = $method;
230 148
        $this->setUri($uri);
231
232 148
        $this->registerStream($body);
233 148
        $this->registerHeaders($headers);
234 148
        $this->registerProtocolVersion($protocol);
235
236 148
        if (!$this->hasHeader('host')) {
237 148
            $this->updateHostHeaderFromUri();
238
        }
239 148
    }
240
241
    /**
242
     * @param UriInterface|string $uri
243
     * @throws InvalidArgumentException for invalid URI.
244
     * @psalm-suppress RedundantConditionGivenDocblockType
245
     */
246 148
    private function setUri($uri): void
247
    {
248 148
        if ($uri instanceof UriInterface) {
249 7
            $this->uri = $uri;
250 7
            return;
251
        }
252
253 141
        if (is_string($uri)) {
0 ignored issues
show
introduced by devanych
The condition is_string($uri) is always true.
Loading history...
254 141
            $this->uri = new Uri($uri);
255 141
            return;
256
        }
257
258 16
        throw new InvalidArgumentException(sprintf(
259
            '"%s" is not valid URI. It must be a null, a string, or a "\Psr\Http\Message\UriInterface" instance.',
260 16
            (is_object($uri) ? get_class($uri) : gettype($uri))
261
        ));
262
    }
263
264
    /**
265
     * Updates `Host` header from the current URI and sets the `Host` first in the list of headers.
266
     *
267
     * @see https://tools.ietf.org/html/rfc7230#section-5.4
268
     */
269 148
    private function updateHostHeaderFromUri(): void
270
    {
271 148
        if (!$host = $this->uri->getHost()) {
272 117
            return;
273
        }
274
275 36
        if ($port = $this->uri->getPort()) {
276 2
            $host .= ':' . $port;
277
        }
278
279 36
        $this->headerNames['host'] ??= 'Host';
280 36
        $this->headers = [$this->headerNames['host'] => [$host]] + $this->headers;
281 36
    }
282
}
283