RequestTrait   A
last analyzed

Complexity

Total Complexity 29

Size/Duplication

Total Lines 259
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 61
c 1
b 0
f 0
dl 0
loc 259
ccs 69
cts 69
cp 1
rs 10
wmc 29

9 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 16 2
A updateHostHeaderFromUri() 0 14 3
A getUri() 0 3 1
A getRequestTarget() 0 14 5
A getMethod() 0 3 1
A withUri() 0 14 4
A setUri() 0 15 4
A withRequestTarget() 0 16 5
A withMethod() 0 16 4
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 11
    public function getRequestTarget(): string
60
    {
61 11
        if ($this->requestTarget !== null) {
62 4
            return $this->requestTarget;
63
        }
64
65 9
        $target = $this->uri->getPath();
66 9
        $query = $this->uri->getQuery();
67
68 9
        if ($target !== '' && $query !== '') {
69 2
            $target .= '?' . $query;
70
        }
71
72 9
        return $target ?: '/';
73
    }
74
75
    /**
76
     * Return an instance with the specific request-target.
77
     *
78
     * If the request needs a non-origin-form request-target — e.g., for
79
     * specifying an absolute-form, authority-form, or asterisk-form —
80
     * this method may be used to create an instance with the specified
81
     * request-target, verbatim.
82
     *
83
     * This method MUST be implemented in such a way as to retain the
84
     * immutability of the message, and MUST return an instance that has the
85
     * changed request target.
86
     *
87
     * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
88
     *     request-target forms allowed in request messages)
89
     * @param mixed $requestTarget
90
     * @return static
91
     */
92 16
    public function withRequestTarget($requestTarget): RequestInterface
93
    {
94 16
        if ($requestTarget === $this->requestTarget) {
95 2
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
96
        }
97
98 14
        if (!is_string($requestTarget) || preg_match('/\s/', $requestTarget)) {
99 10
            throw new InvalidArgumentException(sprintf(
100 10
                '"%s" is not valid request target. Request target must be a string and cannot contain whitespace.',
101 10
                (is_object($requestTarget) ? get_class($requestTarget) : gettype($requestTarget))
102 10
            ));
103
        }
104
105 4
        $new = clone $this;
106 4
        $new->requestTarget = $requestTarget;
107 4
        return $new;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $new returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
108
    }
109
110
    /**
111
     * Retrieves the HTTP method of the request.
112
     *
113
     * @return string Returns the request method.
114
     */
115 14
    public function getMethod(): string
116
    {
117 14
        return $this->method;
118
    }
119
120
    /**
121
     * Return an instance with the provided HTTP method.
122
     *
123
     * While HTTP method names are typically all uppercase characters, HTTP
124
     * method names are case-sensitive and thus implementations SHOULD NOT
125
     * modify the given string.
126
     *
127
     * This method MUST be implemented in such a way as to retain the
128
     * immutability of the message, and MUST return an instance that has the
129
     * changed request method.
130
     *
131
     * @param string $method Case-sensitive method.
132
     * @return static
133
     * @throws InvalidArgumentException for invalid HTTP methods.
134
     * @psalm-suppress DocblockTypeContradiction
135
     */
136 18
    public function withMethod($method): RequestInterface
137
    {
138 18
        if ($method === $this->method) {
139 2
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
140
        }
141
142 16
        if (!is_string($method)) {
0 ignored issues
show
introduced by
The condition is_string($method) is always true.
Loading history...
143 8
            throw new InvalidArgumentException(sprintf(
144 8
                'Invalid HTTP method. It must be a string, %s received.',
145 8
                (is_object($method) ? get_class($method) : gettype($method))
146 8
            ));
147
        }
148
149 8
        $new = clone $this;
150 8
        $new->method = $method;
151 8
        return $new;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $new returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
152
    }
153
154
    /**
155
     * Retrieves the URI instance.
156
     *
157
     * This method MUST return a UriInterface instance.
158
     *
159
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
160
     * @return UriInterface Returns a UriInterface instance
161
     *     representing the URI of the request.
162
     */
163 11
    public function getUri(): UriInterface
164
    {
165 11
        return $this->uri;
166
    }
167
168
    /**
169
     * Returns an instance with the provided URI.
170
     *
171
     * This method MUST update the Host header of the returned request by
172
     * default if the URI contains a host component. If the URI does not
173
     * contain a host component, any pre-existing Host header MUST be carried
174
     * over to the returned request.
175
     *
176
     * You can opt-in to preserving the original state of the Host header by
177
     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
178
     * `true`, this method interacts with the Host header in the following ways:
179
     *
180
     * - If the Host header is missing or empty, and the new URI contains
181
     *   a host component, this method MUST update the Host header in the returned
182
     *   request.
183
     * - If the Host header is missing or empty, and the new URI does not contain a
184
     *   host component, this method MUST NOT update the Host header in the returned
185
     *   request.
186
     * - If a Host header is present and non-empty, this method MUST NOT update
187
     *   the Host header in the returned request.
188
     *
189
     * This method MUST be implemented in such a way as to retain the
190
     * immutability of the message, and MUST return an instance that has the
191
     * new UriInterface instance.
192
     *
193
     * @link http://tools.ietf.org/html/rfc3986#section-4.3
194
     * @param UriInterface $uri New request URI to use.
195
     * @param bool $preserveHost Preserve the original state of the Host header.
196
     * @return static
197
     * @throws InvalidArgumentException for invalid URI.
198
     */
199 15
    public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
200
    {
201 15
        if ($uri === $this->uri) {
202 1
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
203
        }
204
205 14
        $new = clone $this;
206 14
        $new->uri = $uri;
207
208 14
        if (!$preserveHost || !$this->hasHeader('host')) {
209 14
            $new->updateHostHeaderFromUri();
210
        }
211
212 14
        return $new;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $new returns the type HttpSoft\Message\RequestTrait which is incompatible with the type-hinted return Psr\Http\Message\RequestInterface.
Loading history...
213
    }
214
215
    /**
216
     * @param string $method
217
     * @param UriInterface|string $uri
218
     * @param array $headers
219
     * @param StreamInterface|string|resource|null $body
220
     * @param string $protocol
221
     */
222 229
    private function init(
223
        string $method = 'GET',
224
        $uri = '',
225
        array $headers = [],
226
        $body = null,
227
        string $protocol = '1.1'
228
    ): void {
229 229
        $this->method = $method;
230 229
        $this->setUri($uri);
231
232 229
        $this->registerStream($body);
233 229
        $this->registerHeaders($headers);
234 229
        $this->registerProtocolVersion($protocol);
235
236 229
        if (!$this->hasHeader('host')) {
237 229
            $this->updateHostHeaderFromUri();
238
        }
239
    }
240
241
    /**
242
     * @param UriInterface|string $uri
243
     * @throws InvalidArgumentException for invalid URI.
244
     * @psalm-suppress RedundantConditionGivenDocblockType
245
     */
246 229
    private function setUri($uri): void
247
    {
248 229
        if ($uri instanceof UriInterface) {
249 1
            $this->uri = $uri;
250 1
            return;
251
        }
252
253 229
        if (is_string($uri)) {
0 ignored issues
show
introduced by
The condition is_string($uri) is always true.
Loading history...
254 229
            $this->uri = new Uri($uri);
255 229
            return;
256
        }
257
258 16
        throw new InvalidArgumentException(sprintf(
259 16
            '"%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 16
        ));
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 229
    private function updateHostHeaderFromUri(): void
270
    {
271 229
        $host = $this->uri->getHost();
272
273 229
        if ($host === '') {
274 227
            return;
275
        }
276
277 84
        if ($port = $this->uri->getPort()) {
278 2
            $host .= ':' . $port;
279
        }
280
281 84
        $this->headerNames['host'] ??= 'Host';
282 84
        $this->headers = [$this->headerNames['host'] => [$host]] + $this->headers;
283
    }
284
}
285