Uri   B
last analyzed

Complexity

Total Complexity 51

Size/Duplication

Total Lines 399
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 51
eloc 87
c 1
b 0
f 0
dl 0
loc 399
ccs 94
cts 94
cp 1
rs 7.92

22 Methods

Rating   Name   Duplication   Size   Complexity  
A withPath() 0 11 6
A isStandardPort() 0 13 5
A getPort() 0 4 2
A getAuthority() 0 8 3
A withPort() 0 11 4
A _clone() 0 9 2
A __construct() 0 7 2
A getQuery() 0 3 1
A withFragment() 0 7 2
A filterPath() 0 10 2
A filter() 0 10 2
A format() 0 13 4
A withUserInfo() 0 5 1
A getUserInfo() 0 3 1
A getFragment() 0 3 1
A getPath() 0 3 1
A withHost() 0 3 1
A withQuery() 0 3 1
A withScheme() 0 9 3
A getScheme() 0 3 1
A __toString() 0 13 5
A getHost() 0 3 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.

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
namespace Fyuze\Http\Message;
3
4
use InvalidArgumentException;
5
use Psr\Http\Message\UriInterface;
6
use RuntimeException;
7
8
class Uri implements UriInterface
9
{
10
    /**
11
     * @var string
12
     */
13
    protected $uri;
14
15
    /**
16
     * @var string
17
     */
18
    protected $scheme = '';
19
20
    /**
21
     * @var string
22
     */
23
    protected $authority;
24
25
    /**
26
     * @var string
27
     */
28
    protected $user;
29
30
    /**
31
     * @var string
32
     */
33
    protected $pass;
34
35
    /**
36
     * @var string
37
     */
38
    protected $userInfo;
39
40
    /**
41
     * @var string
42
     */
43
    protected $host;
44
45
    /**
46
     * @var int
47
     */
48
    protected $port;
49
50
    /**
51
     * @var string
52
     */
53
    protected $path;
54
55
    /**
56
     * @var string
57
     */
58
    protected $query = '';
59
60
    /**
61
     * @var string
62
     */
63
    protected $fragment;
64
65
    /**
66
     * @var array
67
     */
68
    protected $schemes = [
69
        'http' => 80,
70
        'https' => 443,
71
    ];
72
73
    /**
74
     * @param $uri
75
     */
76 43
    public function __construct($uri = '')
77
    {
78 43
        if ($uri !== '') {
79 25
            $this->format($uri);
80
        }
81
82 43
        $this->uri = $uri;
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     *
88
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
89
     * @return string The URI scheme.
90
     */
91 12
    public function getScheme()
92
    {
93 12
        return $this->scheme;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     *
99
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
100
     * @return string The URI authority, in "[user-info@]host[:port]" format.
101
     */
102 9
    public function getAuthority()
103
    {
104 9
        $userInfo = $this->getUserInfo();
105
106 9
        return rtrim(($userInfo ? $userInfo . '@' : '')
107 9
            . $this->host
108 9
            . (!$this->isStandardPort($this->scheme, $this->port) ? ':' . $this->port : ''),
109 9
            ':');
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     *
115
     * @return string The URI user information, in "username[:password]" format.
116
     */
117 12
    public function getUserInfo()
118
    {
119 12
        return $this->userInfo;
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     *
125
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
126
     * @return string The URI host.
127
     */
128 23
    public function getHost()
129
    {
130 23
        return $this->host;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     *
136
     * @return null|int The URI port.
137
     */
138 6
    public function getPort()
139
    {
140 6
        return $this->isStandardPort($this->scheme, $this->port)
141 6
            ? null : $this->port;
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     *
147
     * @see https://tools.ietf.org/html/rfc3986#section-2
148
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
149
     * @return string The URI path.
150
     */
151 23
    public function getPath()
152
    {
153 23
        return $this->path;
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     *
159
     * @see https://tools.ietf.org/html/rfc3986#section-2
160
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
161
     * @return string The URI query string.
162
     */
163 15
    public function getQuery()
164
    {
165 15
        return $this->query;
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     *
171
     * @see https://tools.ietf.org/html/rfc3986#section-2
172
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
173
     * @return string The URI fragment.
174
     */
175 15
    public function getFragment()
176
    {
177 15
        return $this->fragment;
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     *
183
     * @param string $scheme The scheme to use with the new instance.
184
     * @return self A new instance with the specified scheme.
185
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
186
     */
187 6
    public function withScheme($scheme)
188
    {
189 6
        $scheme = str_replace('://', '', strtolower((string)$scheme));
190
191 6
        if (!empty($scheme) && !array_key_exists($scheme, $this->schemes)) {
192 1
            throw new InvalidArgumentException('Invalid scheme provided.');
193
        }
194
195 5
        return $this->_clone('scheme', $scheme);
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     *
201
     * @param string $user The user name to use for authority.
202
     * @param null|string $password The password associated with $user.
203
     * @return self A new instance with the specified user information.
204
     */
205 4
    public function withUserInfo($user, $password = null)
206
    {
207 4
        $userInfo = implode(':', [$user, $password]);
208
209 4
        return $this->_clone('userInfo', rtrim($userInfo, ':'));
210
    }
211
212
    /**
213
     * {@inheritdoc}
214
     *
215
     * @param string $host
216
     * @return Uri
217
     */
218 3
    public function withHost($host)
219
    {
220 3
        return $this->_clone('host', $host);
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     *
226
     * @param null|int $port The port to use with the new instance; a null value
227
     *     removes the port information.
228
     * @return self A new instance with the specified port.
229
     * @throws InvalidArgumentException for invalid ports.
230
     */
231 5
    public function withPort($port)
232
    {
233 5
        if ($port === null) {
234 1
            return $this->_clone('port', null);
235
        }
236
237 5
        if ($port < 1 || $port > 65535) {
238 1
            throw new InvalidArgumentException('Invalid port specified');
239
        }
240
241 4
        return $this->_clone('port', (int)$port);
242
    }
243
244
    /**
245
     * {@inheritdoc}
246
     *
247
     * @param string $path The path to use with the new instance.
248
     * @return self A new instance with the specified path.
249
     * @throws \InvalidArgumentException for invalid paths.
250
     */
251 7
    public function withPath($path)
252
    {
253 7
        if (!is_string($path) || strpos($path, '#') !== false || strpos($path, '?') !== false) {
0 ignored issues
show
introduced by
The condition is_string($path) is always true.
Loading history...
254 2
            throw new \InvalidArgumentException('Path must be a string and cannot contain a fragment or query string');
255
        }
256
257 5
        if (!empty($path) && '/' !== substr($path, 0, 1)) {
258 4
            $path = '/' . $path;
259
        }
260
261 5
        return $this->_clone('path', $this->filterPath($path));
262
    }
263
264
    /**
265
     * {@inheritdoc}
266
     *
267
     * @param string $query The query string to use with the new instance.
268
     * @return self A new instance with the specified query string.
269
     * @throws \InvalidArgumentException for invalid query strings.
270
     */
271 5
    public function withQuery($query)
272
    {
273 5
        return $this->_clone('query', $this->filter($query));
274
    }
275
276
    /**
277
     * {@inheritdoc}
278
     *
279
     * @param string $fragment The fragment to use with the new instance.
280
     * @return self A new instance with the specified fragment.
281
     */
282 7
    public function withFragment($fragment)
283
    {
284 7
        if (strpos($fragment, '#') === 0) {
285 2
            $fragment = substr($fragment, 1);
286
        }
287
288 7
        return $this->_clone('fragment', $this->filter($fragment));
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     *
294
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
295
     * @return string
296
     */
297 8
    public function __toString()
298
    {
299 8
        $scheme = $this->getScheme();
300 8
        $authority = $this->getAuthority();
301 8
        $path = $this->getPath();
302 8
        $query = $this->getQuery();
303 8
        $fragment = $this->getFragment();
304
305 8
        return ($scheme ? $scheme . '://' : '')
306 8
        . ($authority ?: '')
307 8
        . $path
308 8
        . ($query ? '?' . $query : '')
309 8
        . ($fragment ? '#' . $fragment : '');
310
    }
311
312
    /**
313
     * @param $uri
314
     */
315 25
    protected function format($uri)
316
    {
317 25
        $parsed = parse_url($uri);
318
319 25
        if (false === $parsed) {
320 1
            throw new RuntimeException('Seriously malformed url');
321
        }
322
323 24
        foreach (parse_url($uri) as $key => $val) {
324 24
            $this->$key = $val;
325
        }
326
327 24
        $this->userInfo = $this->user . ($this->pass ? ":$this->pass" : null);
328
    }
329
330
331
    /**
332
     * Filters the query string or fragment of a URI.
333
     *
334
     * @param string $query The raw uri query string.
335
     * @return string The percent-encoded query string.
336
     */
337 7
    protected function filter($query)
338
    {
339 7
        $decoded = rawurldecode($query);
340
341 7
        if ($decoded === $query) {
342
            // Query string or fragment is already decoded, encode
343 6
            return str_replace(['%3D', '%26'], ['=', '&'], rawurlencode($query));
344
        }
345
346 1
        return $query;
347
    }
348
349
    /**
350
     * Filter Uri path.
351
     *
352
     * This method percent-encodes all reserved
353
     * characters in the provided path string. This method
354
     * will NOT double-encode characters that are already
355
     * percent-encoded.
356
     *
357
     * @param  string $path The raw uri path.
358
     * @return string       The RFC 3986 percent-encoded uri path.
359
     * @link   http://www.faqs.org/rfcs/rfc3986.html
360
     */
361 5
    protected function filterPath($path)
362
    {
363 5
        $decoded = rawurldecode($path);
364
365 5
        if ($decoded === $path) {
366
            // Url is already decoded, encode
367 4
            return str_replace('%2F', '/', rawurlencode($path));
368
        }
369
370 1
        return $path;
371
    }
372
373
    /**
374
     * @param $scheme
375
     * @param $port
376
     * @return bool
377
     */
378 14
    protected function isStandardPort($scheme, $port)
379
    {
380 14
        if (!$scheme && !$port) {
381 8
            return true;
382
        }
383
384 7
        if (array_key_exists($scheme, $this->schemes)
385 7
            && $port === $this->schemes[$scheme]
386
        ) {
387 1
            return true;
388
        }
389
390 6
        return false;
391
    }
392
393
    /**
394
     * @param $key
395
     * @param $value
396
     * @return static
397
     */
398 14
    protected function _clone($key, $value)
399
    {
400 14
        if ($this->$key === $value) {
401 1
            return clone($this);
402
        }
403
404 14
        $instance = clone $this;
405 14
        $instance->$key = $value;
406 14
        return $instance;
407
    }
408
}
409