Request   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 57
c 1
b 0
f 0
dl 0
loc 214
ccs 65
cts 65
cp 1
rs 10
wmc 28

12 Methods

Rating   Name   Duplication   Size   Complexity  
A withUri() 0 6 1
A validateRequestTarget() 0 12 4
A getRequestTarget() 0 23 4
A __construct() 0 18 4
A withRequestTarget() 0 6 1
A validateMethod() 0 12 4
A withMethod() 0 6 1
A getUri() 0 3 1
A setRequestTarget() 0 7 1
A getMethod() 0 3 1
A setMethod() 0 5 1
A setUri() 0 19 5
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Nekhay <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Nekhay
8
 * @license https://github.com/sunrise-php/http-message/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-message
10
 */
11
12
namespace Sunrise\Http\Message;
13
14
use Fig\Http\Message\RequestMethodInterface;
15
use Psr\Http\Message\RequestInterface;
16
use Psr\Http\Message\StreamInterface;
17
use Psr\Http\Message\UriInterface;
18
use Sunrise\Http\Message\Exception\InvalidArgumentException;
19
20
use function is_string;
21
use function preg_match;
22
use function strncmp;
23
24
class Request extends Message implements RequestInterface, RequestMethodInterface
25
{
26
    private const RFC7230_REQUEST_TARGET_REGEX = '/^[\x21-\x7E\x80-\xFF]+$/';
27
28
    private string $method = self::METHOD_GET;
29
    private UriInterface $uri;
30
    private ?string $requestTarget = null;
31
32
    /**
33
     * @param mixed $uri
34
     * @param array<string, string|string[]>|null $headers
35
     *
36
     * @throws InvalidArgumentException
37
     */
38 489
    public function __construct(
39
        ?string $method = null,
40
        $uri = null,
41
        ?array $headers = null,
42
        ?StreamInterface $body = null
43
    ) {
44 489
        if ($method !== null) {
45 84
            $this->setMethod($method);
46
        }
47
48 479
        $this->setUri($uri ?? '/');
49
50 475
        if ($headers !== null) {
51 85
            $this->setHeaders($headers);
52
        }
53
54 453
        if ($body !== null) {
55 38
            $this->setBody($body);
56
        }
57
    }
58
59
    /**
60
     * @inheritDoc
61
     */
62 31
    public function getMethod(): string
63
    {
64 31
        return $this->method;
65
    }
66
67
    /**
68
     * @inheritDoc
69
     */
70 26
    public function withMethod($method): RequestInterface
71
    {
72 26
        $clone = clone $this;
73 26
        $clone->setMethod($method);
74
75 12
        return $clone;
76
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81 29
    public function getUri(): UriInterface
82
    {
83 29
        return $this->uri;
84
    }
85
86
    /**
87
     * @inheritDoc
88
     */
89 40
    public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
90
    {
91 40
        $clone = clone $this;
92 40
        $clone->setUri($uri, $preserveHost);
93
94 40
        return $clone;
95
    }
96
97
    /**
98
     * @inheritDoc
99
     */
100 34
    public function getRequestTarget(): string
101
    {
102 34
        if ($this->requestTarget !== null) {
103 12
            return $this->requestTarget;
104
        }
105
106 32
        $requestTarget = $this->uri->getPath();
107
108
        // https://tools.ietf.org/html/rfc7230#section-5.3.1
109
        // https://tools.ietf.org/html/rfc7230#section-2.7
110
        //
111
        // origin-form = absolute-path [ "?" query ]
112
        // absolute-path = 1*( "/" segment )
113 32
        if (strncmp($requestTarget, '/', 1) !== 0) {
114 4
            return '/';
115
        }
116
117 28
        $queryString = $this->uri->getQuery();
118 28
        if ($queryString !== '') {
119 4
            $requestTarget .= '?' . $queryString;
120
        }
121
122 28
        return $requestTarget;
123
    }
124
125
    /**
126
     * @inheritDoc
127
     */
128 18
    public function withRequestTarget($requestTarget): RequestInterface
129
    {
130 18
        $clone = clone $this;
131 18
        $clone->setRequestTarget($requestTarget);
132
133 12
        return $clone;
134
    }
135
136
    /**
137
     * Sets the given method to the request
138
     *
139
     * @param string $method
140
     *
141
     * @throws InvalidArgumentException
142
     */
143 110
    final protected function setMethod($method): void
144
    {
145 110
        $this->validateMethod($method);
146
147 86
        $this->method = $method;
148
    }
149
150
    /**
151
     * Sets the given URI to the request
152
     *
153
     * @param mixed $uri
154
     * @param bool $preserveHost
155
     *
156
     * @throws InvalidArgumentException
157
     */
158 479
    final protected function setUri($uri, $preserveHost = false): void
159
    {
160 479
        $this->uri = Uri::create($uri);
161
162 475
        if ($preserveHost && $this->hasHeader('Host')) {
163 4
            return;
164
        }
165
166 475
        $host = $this->uri->getHost();
167 475
        if ($host === '') {
168 434
            return;
169
        }
170
171 63
        $port = $this->uri->getPort();
172 63
        if ($port !== null) {
173 6
            $host .= ':' . (string) $port;
174
        }
175
176 63
        $this->setHeader('Host', $host, true);
177
    }
178
179
    /**
180
     * Sets the given request target to the request
181
     *
182
     * @param mixed $requestTarget
183
     *
184
     * @throws InvalidArgumentException
185
     */
186 18
    final protected function setRequestTarget($requestTarget): void
187
    {
188 18
        $this->validateRequestTarget($requestTarget);
189
190
        /** @var string $requestTarget */
191
192 12
        $this->requestTarget = $requestTarget;
193
    }
194
195
    /**
196
     * Validates the given method
197
     *
198
     * @link https://tools.ietf.org/html/rfc7230#section-3.1.1
199
     *
200
     * @param mixed $method
201
     *
202
     * @throws InvalidArgumentException
203
     */
204 110
    private function validateMethod($method): void
205
    {
206 110
        if ($method === '') {
207 7
            throw new InvalidArgumentException('HTTP method cannot be an empty');
208
        }
209
210 103
        if (!is_string($method)) {
211 10
            throw new InvalidArgumentException('HTTP method must be a string');
212
        }
213
214 93
        if (!preg_match(HeaderInterface::RFC7230_TOKEN_REGEX, $method)) {
215 7
            throw new InvalidArgumentException('Invalid HTTP method');
216
        }
217
    }
218
219
    /**
220
     * Validates the given request target
221
     *
222
     * @param mixed $requestTarget
223
     *
224
     * @throws InvalidArgumentException
225
     */
226 18
    private function validateRequestTarget($requestTarget): void
227
    {
228 18
        if ($requestTarget === '') {
229 2
            throw new InvalidArgumentException('HTTP request target cannot be an empty');
230
        }
231
232 16
        if (!is_string($requestTarget)) {
233 2
            throw new InvalidArgumentException('HTTP request target must be a string');
234
        }
235
236 14
        if (!preg_match(self::RFC7230_REQUEST_TARGET_REGEX, $requestTarget)) {
237 2
            throw new InvalidArgumentException('Invalid HTTP request target');
238
        }
239
    }
240
}
241