Uri   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 295
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 1

Test Coverage

Coverage 95.28%

Importance

Changes 0
Metric Value
wmc 63
lcom 3
cbo 1
dl 0
loc 295
ccs 121
cts 127
cp 0.9528
rs 3.36
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A getScheme() 0 4 1
A getAuthority() 0 17 4
A getUserInfo() 0 4 1
A getHost() 0 4 1
A getPort() 0 4 1
A getPath() 0 4 1
A getQuery() 0 4 1
A getFragment() 0 4 1
B __construct() 0 20 10
A __toString() 0 4 1
A withScheme() 0 16 3
A withUserInfo() 0 16 4
A withHost() 0 15 3
A withPort() 0 11 2
A withPath() 0 11 2
A withQuery() 0 11 2
A withFragment() 0 11 2
B createUriString() 0 38 11
A isNonStandardPort() 0 4 2
A filterPort() 0 13 5
A filterPath() 0 8 2
A filterQueryAndFragment() 0 8 2
A rawurlencodeMatchZero() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Uri often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Uri, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7;
6
7
use Psr\Http\Message\UriInterface;
8
9
/**
10
 * PSR-7 URI implementation.
11
 *
12
 * @author Michael Dowling
13
 * @author Tobias Schultze
14
 * @author Matthew Weier O'Phinney
15
 * @author Tobias Nyholm <[email protected]>
16
 * @author Martijn van der Ven <[email protected]>
17
 */
18
final class Uri implements UriInterface
19
{
20
    use LowercaseTrait;
21
22
    private const SCHEMES = ['http' => 80, 'https' => 443];
23
24
    private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
25
26
    private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
27
28
    /** @var string Uri scheme. */
29
    private $scheme = '';
30
31
    /** @var string Uri user info. */
32
    private $userInfo = '';
33
34
    /** @var string Uri host. */
35
    private $host = '';
36
37
    /** @var int|null Uri port. */
38
    private $port;
39
40
    /** @var string Uri path. */
41
    private $path = '';
42
43
    /** @var string Uri query string. */
44
    private $query = '';
45
46
    /** @var string Uri fragment. */
47
    private $fragment = '';
48
49 151
    public function __construct(string $uri = '')
50
    {
51 151
        if ('' !== $uri) {
52 131
            if (false === $parts = \parse_url($uri)) {
53 4
                throw new \InvalidArgumentException("Unable to parse URI: $uri");
54
            }
55
56
            // Apply parse_url parts to a URI.
57 127
            $this->scheme = isset($parts['scheme']) ? self::lowercase($parts['scheme']) : '';
58 127
            $this->userInfo = $parts['user'] ?? '';
59 127
            $this->host = isset($parts['host']) ? self::lowercase($parts['host']) : '';
60 127
            $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
61 127
            $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
62 127
            $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
63 127
            $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
64 127
            if (isset($parts['pass'])) {
65 5
                $this->userInfo .= ':' . $parts['pass'];
66
            }
67
        }
68 147
    }
69
70 68
    public function __toString(): string
71
    {
72 68
        return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment);
73
    }
74
75 8
    public function getScheme(): string
76
    {
77 8
        return $this->scheme;
78
    }
79
80 74
    public function getAuthority(): string
81
    {
82 74
        if ('' === $this->host) {
83 29
            return '';
84
        }
85
86 47
        $authority = $this->host;
87 47
        if ('' !== $this->userInfo) {
88 7
            $authority = $this->userInfo . '@' . $authority;
89
        }
90
91 47
        if (null !== $this->port) {
92 7
            $authority .= ':' . $this->port;
93
        }
94
95 47
        return $authority;
96
    }
97
98 7
    public function getUserInfo(): string
99
    {
100 7
        return $this->userInfo;
101
    }
102
103 91
    public function getHost(): string
104
    {
105 91
        return $this->host;
106
    }
107
108 42
    public function getPort(): ?int
109
    {
110 42
        return $this->port;
111
    }
112
113 23
    public function getPath(): string
114
    {
115 23
        return $this->path;
116
    }
117
118 19
    public function getQuery(): string
119
    {
120 19
        return $this->query;
121
    }
122
123 17
    public function getFragment(): string
124
    {
125 17
        return $this->fragment;
126
    }
127
128 12
    public function withScheme($scheme): self
129
    {
130 12
        if (!\is_string($scheme)) {
131 5
            throw new \InvalidArgumentException('Scheme must be a string');
132
        }
133
134 7
        if ($this->scheme === $scheme = self::lowercase($scheme)) {
135
            return $this;
136
        }
137
138 7
        $new = clone $this;
139 7
        $new->scheme = $scheme;
140 7
        $new->port = $new->filterPort($new->port);
141
142 7
        return $new;
143
    }
144
145 5
    public function withUserInfo($user, $password = null): self
146
    {
147 5
        $info = $user;
148 5
        if (null !== $password && '' !== $password) {
149 5
            $info .= ':' . $password;
150
        }
151
152 5
        if ($this->userInfo === $info) {
153
            return $this;
154
        }
155
156 5
        $new = clone $this;
157 5
        $new->userInfo = $info;
158
159 5
        return $new;
160
    }
161
162 8
    public function withHost($host): self
163
    {
164 8
        if (!\is_string($host)) {
165 1
            throw new \InvalidArgumentException('Host must be a string');
166
        }
167
168 7
        if ($this->host === $host = self::lowercase($host)) {
169
            return $this;
170
        }
171
172 7
        $new = clone $this;
173 7
        $new->host = $host;
174
175 7
        return $new;
176
    }
177
178 9
    public function withPort($port): self
179
    {
180 9
        if ($this->port === $port = $this->filterPort($port)) {
181 1
            return $this;
182
        }
183
184 6
        $new = clone $this;
185 6
        $new->port = $port;
186
187 6
        return $new;
188
    }
189
190 9
    public function withPath($path): self
191
    {
192 9
        if ($this->path === $path = $this->filterPath($path)) {
193
            return $this;
194
        }
195
196 8
        $new = clone $this;
197 8
        $new->path = $path;
198
199 8
        return $new;
200
    }
201
202 6
    public function withQuery($query): self
203
    {
204 6
        if ($this->query === $query = $this->filterQueryAndFragment($query)) {
205
            return $this;
206
        }
207
208 5
        $new = clone $this;
209 5
        $new->query = $query;
210
211 5
        return $new;
212
    }
213
214 6
    public function withFragment($fragment): self
215
    {
216 6
        if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) {
217
            return $this;
218
        }
219
220 5
        $new = clone $this;
221 5
        $new->fragment = $fragment;
222
223 5
        return $new;
224
    }
225
226
    /**
227
     * Create a URI string from its various parts.
228
     */
229 68
    private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment): string
230
    {
231 68
        $uri = '';
232 68
        if ('' !== $scheme) {
233 42
            $uri .= $scheme . ':';
234
        }
235
236 68
        if ('' !== $authority) {
237 43
            $uri .= '//' . $authority;
238
        }
239
240 68
        if ('' !== $path) {
241 54
            if ('/' !== $path[0]) {
242 7
                if ('' !== $authority) {
243
                    // If the path is rootless and an authority is present, the path MUST be prefixed by "/"
244 7
                    $path = '/' . $path;
245
                }
246 47
            } elseif (isset($path[1]) && '/' === $path[1]) {
247 1
                if ('' === $authority) {
248
                    // If the path is starting with more than one "/" and no authority is present, the
249
                    // starting slashes MUST be reduced to one.
250 1
                    $path = '/' . \ltrim($path, '/');
251
                }
252
            }
253
254 54
            $uri .= $path;
255
        }
256
257 68
        if ('' !== $query) {
258 35
            $uri .= '?' . $query;
259
        }
260
261 68
        if ('' !== $fragment) {
262 16
            $uri .= '#' . $fragment;
263
        }
264
265 68
        return $uri;
266
    }
267
268
    /**
269
     * Is a given port non-standard for the current scheme?
270
     */
271 12
    private static function isNonStandardPort(string $scheme, int $port): bool
272
    {
273 12
        return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme];
274
    }
275
276 17
    private function filterPort($port): ?int
277
    {
278 17
        if (null === $port) {
279 6
            return null;
280
        }
281
282 14
        $port = (int) $port;
283 14
        if (0 > $port || 0xffff < $port) {
284 2
            throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port));
285
        }
286
287 12
        return self::isNonStandardPort($this->scheme, $port) ? $port : null;
288
    }
289
290 122
    private function filterPath($path): string
291
    {
292 122
        if (!\is_string($path)) {
293 1
            throw new \InvalidArgumentException('Path must be a string');
294
        }
295
296 121
        return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path);
297
    }
298
299 40
    private function filterQueryAndFragment($str): string
300
    {
301 40
        if (!\is_string($str)) {
302 2
            throw new \InvalidArgumentException('Query and fragment must be a string');
303
        }
304
305 38
        return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str);
306
    }
307
308 6
    private static function rawurlencodeMatchZero(array $match): string
309
    {
310 6
        return \rawurlencode($match[0]);
311
    }
312
}
313