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

src/RequestTrait.php (8 issues)

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 6
    public function getRequestTarget(): string
60
    {
61 6
        if ($this->requestTarget !== null) {
62 2
            return $this->requestTarget;
63
        }
64
65 4
        $target = $this->uri->getPath();
66
67 4
        if ($target && $query = $this->uri->getQuery()) {
68
            $target .= '?' . $query;
69
        }
70
71 4
        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 14
    public function withRequestTarget($requestTarget): RequestInterface
92
    {
93 14
        if ($requestTarget === $this->requestTarget) {
94 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...
95
        }
96
97 12
        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 2
        $new = clone $this;
105 2
        $new->requestTarget = $requestTarget;
106 2
        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...
107
    }
108
109
    /**
110
     * Retrieves the HTTP method of the request.
111
     *
112
     * @return string Returns the request method.
113
     */
114 7
    public function getMethod(): string
115
    {
116 7
        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 4
    public function withMethod($method): RequestInterface
136
    {
137 4
        if ($method === $this->method) {
138 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...
139
        }
140
141 2
        if (!is_string($method)) {
0 ignored issues
show
The condition is_string($method) is always true.
Loading history...
142
            throw new InvalidArgumentException(sprintf(
143
                'Invalid HTTP method. Must be a string type, received `%s`.',
144
                (is_object($method) ? get_class($method) : gettype($method))
145
            ));
146
        }
147
148 2
        $new = clone $this;
149 2
        $new->method = $method;
150 2
        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...
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 7
    public function getUri(): UriInterface
163
    {
164 7
        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 4
    public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
199
    {
200 4
        if ($uri === $this->uri) {
201
            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...
202
        }
203
204 4
        $new = clone $this;
205 4
        $new->uri = $uri;
206
207 4
        if (!$preserveHost || !$this->hasHeader('host')) {
208 4
            $new->updateHostHeaderFromUri();
209
        }
210
211 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...
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 64
    private function init(
223
        string $method = 'GET',
224
        $uri = '',
225
        array $headers = [],
226
        $body = 'php://temp',
227
        string $protocol = '1.1'
228
    ): void {
229 64
        $this->method = $method;
230 64
        $this->setUri($uri);
231
232 64
        $this->registerStream($body);
233 64
        $this->registerHeaders($headers);
234 64
        $this->registerProtocolVersion($protocol);
235
236 64
        if (!$this->hasHeader('host')) {
237 64
            $this->updateHostHeaderFromUri();
238
        }
239 64
    }
240
241
    /**
242
     * @param UriInterface|string $uri
243
     * @throws InvalidArgumentException for invalid URI.
244
     * @psalm-suppress RedundantConditionGivenDocblockType
245
     */
246 64
    private function setUri($uri): void
247
    {
248 64
        if ($uri instanceof UriInterface) {
249
            $this->uri = $uri;
250
            return;
251
        }
252
253 64
        if (is_string($uri)) {
0 ignored issues
show
The condition is_string($uri) is always true.
Loading history...
254 64
            $this->uri = new Uri($uri);
255 64
            return;
256
        }
257
258 16
        throw new InvalidArgumentException(sprintf(
259
            '`%s` is not valid URI. Must be `null` or 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 64
    private function updateHostHeaderFromUri(): void
270
    {
271 64
        if (!$host = $this->uri->getHost()) {
272 62
            return;
273
        }
274
275 4
        if ($port = $this->uri->getPort()) {
276 2
            $host .= ':' . $port;
277
        }
278
279 4
        $this->headerNames['host'] ??= 'Host';
280 4
        $this->headers = [$this->headerNames['host'] => [$host]] + $this->headers;
281 4
    }
282
}
283