Passed
Pull Request — master (#27)
by Anatoly
04:06
created

Uri   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 490
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 111
c 1
b 0
f 0
dl 0
loc 490
ccs 130
cts 130
cp 1
rs 6.96
wmc 53

25 Methods

Rating   Name   Duplication   Size   Complexity  
A withPath() 0 6 1
B __toString() 0 49 10
A withScheme() 0 6 1
A setPath() 0 5 1
A setHost() 0 5 1
A setUserInfo() 0 5 1
A withFragment() 0 6 1
A setPort() 0 5 1
A getPath() 0 8 2
A setScheme() 0 5 1
A getHost() 0 3 1
A withUserInfo() 0 6 1
A withHost() 0 6 1
A getFragment() 0 3 1
A getAuthority() 0 18 4
A create() 0 11 3
A getUserInfo() 0 3 1
A getQuery() 0 3 1
A setFragment() 0 5 1
B __construct() 0 40 10
A withPort() 0 6 1
A getScheme() 0 3 1
A getPort() 0 13 5
A setQuery() 0 5 1
A withQuery() 0 6 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 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
/**
15
 * Import classes
16
 */
17
use Psr\Http\Message\UriInterface;
18
use Sunrise\Http\Message\Exception\InvalidArgumentException;
19
use Sunrise\Http\Message\Exception\InvalidUriComponentException;
20
use Sunrise\Http\Message\Exception\InvalidUriException;
21
use Sunrise\Http\Message\Uri\Component\Fragment;
22
use Sunrise\Http\Message\Uri\Component\Host;
23
use Sunrise\Http\Message\Uri\Component\Path;
24
use Sunrise\Http\Message\Uri\Component\Port;
25
use Sunrise\Http\Message\Uri\Component\Query;
26
use Sunrise\Http\Message\Uri\Component\Scheme;
27
use Sunrise\Http\Message\Uri\Component\UserInfo;
28
29
/**
30
 * Import functions
31
 */
32
use function is_string;
33
use function ltrim;
34
use function parse_url;
35
use function strncmp;
36
37
/**
38
 * Uniform Resource Identifier
39
 *
40
 * @link https://tools.ietf.org/html/rfc3986
41
 * @link https://www.php-fig.org/psr/psr-7/
42
 */
43
class Uri implements UriInterface
44
{
45
46
    /**
47
     * Scheme of the URI
48
     *
49
     * @var string
50
     */
51
    private string $scheme = '';
52
53
    /**
54
     * User Information of the URI
55
     *
56
     * @var string
57
     */
58
    private string $userInfo = '';
59
60
    /**
61
     * Host of the URI
62
     *
63
     * @var string
64
     */
65
    private string $host = '';
66
67
    /**
68
     * Port of the URI
69
     *
70
     * @var int|null
71
     */
72
    private ?int $port = null;
73
74
    /**
75
     * Path of the URI
76
     *
77
     * @var string
78
     */
79
    private string $path = '';
80
81
    /**
82
     * Query of the URI
83
     *
84
     * @var string
85
     */
86
    private string $query = '';
87
88
    /**
89
     * Fragment of the URI
90
     *
91
     * @var string
92
     */
93
    private string $fragment = '';
94
95
    /**
96
     * Constructor of the class
97
     *
98
     * @param string $uri
99
     *
100
     * @throws InvalidUriException
101
     *         If the URI isn't valid.
102
     */
103 603
    public function __construct(string $uri = '')
104
    {
105 603
        if ($uri === '') {
106 100
            return;
107
        }
108
109 507
        $components = parse_url($uri);
110 507
        if ($components === false) {
111 6
            throw new InvalidUriException('Unable to parse URI');
112
        }
113
114 501
        if (isset($components['scheme'])) {
115 103
            $this->setScheme($components['scheme']);
116
        }
117
118 501
        if (isset($components['user'])) {
119 43
            $this->setUserInfo(
120 43
                $components['user'],
121 43
                $components['pass'] ?? null
122 43
            );
123
        }
124
125 501
        if (isset($components['host'])) {
126 118
            $this->setHost($components['host']);
127
        }
128
129 501
        if (isset($components['port'])) {
130 48
            $this->setPort($components['port']);
131
        }
132
133 501
        if (isset($components['path'])) {
134 491
            $this->setPath($components['path']);
135
        }
136
137 501
        if (isset($components['query'])) {
138 47
            $this->setQuery($components['query']);
139
        }
140
141 501
        if (isset($components['fragment'])) {
142 40
            $this->setFragment($components['fragment']);
143
        }
144
    }
145
146
    /**
147
     * Creates a URI
148
     *
149
     * @param mixed $uri
150
     *
151
     * @return UriInterface
152
     *
153
     * @throws InvalidArgumentException
154
     *         If a URI cannot be created.
155
     */
156 463
    public static function create($uri): UriInterface
157
    {
158 463
        if ($uri instanceof UriInterface) {
159 129
            return $uri;
160
        }
161
162 369
        if (!is_string($uri)) {
163 1
            throw new InvalidArgumentException('URI should be a string');
164
        }
165
166 368
        return new self($uri);
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     *
172
     * @throws InvalidUriComponentException
173
     *         If the scheme isn't valid.
174
     */
175 19
    public function withScheme($scheme): UriInterface
176
    {
177 19
        $clone = clone $this;
178 19
        $clone->setScheme($scheme);
179
180 5
        return $clone;
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     *
186
     * @throws InvalidUriComponentException
187
     *         If the user information isn't valid.
188
     */
189 23
    public function withUserInfo($user, $password = null): UriInterface
190
    {
191 23
        $clone = clone $this;
192 23
        $clone->setUserInfo($user, $password);
193
194 6
        return $clone;
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     *
200
     * @throws InvalidUriComponentException
201
     *         If the host isn't valid.
202
     */
203 15
    public function withHost($host): UriInterface
204
    {
205 15
        $clone = clone $this;
206 15
        $clone->setHost($host);
207
208 6
        return $clone;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     *
214
     * @throws InvalidUriComponentException
215
     *         If the port isn't valid.
216
     */
217 15
    public function withPort($port): UriInterface
218
    {
219 15
        $clone = clone $this;
220 15
        $clone->setPort($port);
221
222 4
        return $clone;
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     *
228
     * @throws InvalidUriComponentException
229
     *         If the path isn't valid.
230
     */
231 14
    public function withPath($path): UriInterface
232
    {
233 14
        $clone = clone $this;
234 14
        $clone->setPath($path);
235
236 5
        return $clone;
237
    }
238
239
    /**
240
     * {@inheritdoc}
241
     *
242
     * @throws InvalidUriComponentException
243
     *         If the query isn't valid.
244
     */
245 13
    public function withQuery($query): UriInterface
246
    {
247 13
        $clone = clone $this;
248 13
        $clone->setQuery($query);
249
250 4
        return $clone;
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     *
256
     * @throws InvalidUriComponentException
257
     *         If the fragment isn't valid.
258
     */
259 13
    public function withFragment($fragment): UriInterface
260
    {
261 13
        $clone = clone $this;
262 13
        $clone->setFragment($fragment);
263
264 4
        return $clone;
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     */
270 18
    public function getScheme(): string
271
    {
272 18
        return $this->scheme;
273
    }
274
275
    /**
276
     * {@inheritdoc}
277
     */
278 6
    public function getUserInfo(): string
279
    {
280 6
        return $this->userInfo;
281
    }
282
283
    /**
284
     * {@inheritdoc}
285
     */
286 432
    public function getHost(): string
287
    {
288 432
        return $this->host;
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294 78
    public function getPort(): ?int
295
    {
296
        // The 80 is the default port number for the HTTP protocol.
297 78
        if ($this->port === 80 && $this->scheme === 'http') {
298 3
            return null;
299
        }
300
301
        // The 443 is the default port number for the HTTPS protocol.
302 78
        if ($this->port === 443 && $this->scheme === 'https') {
303 2
            return null;
304
        }
305
306 78
        return $this->port;
307
    }
308
309
    /**
310
     * {@inheritdoc}
311
     */
312 49
    public function getPath(): string
313
    {
314
        // CVE-2015-3257
315 49
        if (strncmp($this->path, '//', 2) === 0) {
316 7
            return '/' . ltrim($this->path, '/');
317
        }
318
319 42
        return $this->path;
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325 36
    public function getQuery(): string
326
    {
327 36
        return $this->query;
328
    }
329
330
    /**
331
     * {@inheritdoc}
332
     */
333 8
    public function getFragment(): string
334
    {
335 8
        return $this->fragment;
336
    }
337
338
    /**
339
     * {@inheritdoc}
340
     */
341 52
    public function getAuthority(): string
342
    {
343
        // The host is the basic subcomponent.
344 52
        if ($this->host === '') {
345 29
            return '';
346
        }
347
348 27
        $authority = $this->host;
349 27
        if ($this->userInfo !== '') {
350 7
            $authority = $this->userInfo . '@' . $authority;
351
        }
352
353 27
        $port = $this->getPort();
354 27
        if ($port !== null) {
355 9
            $authority = $authority . ':' . $port;
356
        }
357
358 27
        return $authority;
359
    }
360
361
    /**
362
     * {@inheritdoc}
363
     */
364 50
    public function __toString(): string
365
    {
366 50
        $uri = '';
367
368 50
        $scheme = $this->scheme;
369 50
        if ($scheme !== '') {
370 23
            $uri .= $scheme . ':';
371
        }
372
373 50
        $authority = $this->getAuthority();
374 50
        if ($authority !== '') {
375 25
            $uri .= '//' . $authority;
376
        }
377
378 50
        $path = $this->path;
379 50
        if ($path !== '') {
380
            // https://github.com/sunrise-php/uri/issues/31
381
            // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
382
            //
383
            // If a URI contains an authority component,
384
            // then the path component must either be empty
385
            // or begin with a slash ("/") character.
386 46
            if ($authority !== '' && strncmp($path, '/', 1) !== 0) {
387 1
                $path = '/' . $path;
388
            }
389
390
            // https://github.com/sunrise-php/uri/issues/31
391
            // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
392
            //
393
            // If a URI does not contain an authority component,
394
            // then the path cannot begin with two slash characters ("//").
395 46
            if ($authority === '' && strncmp($path, '//', 2) === 0) {
396 1
                $path = '/' . ltrim($path, '/');
397
            }
398
399 46
            $uri .= $path;
400
        }
401
402 50
        $query = $this->query;
403 50
        if ($query !== '') {
404 6
            $uri .= '?' . $query;
405
        }
406
407 50
        $fragment = $this->fragment;
408 50
        if ($fragment !== '') {
409 4
            $uri .= '#' . $fragment;
410
        }
411
412 50
        return $uri;
413
    }
414
415
    /**
416
     * Sets the given scheme to the URI
417
     *
418
     * @param mixed $scheme
419
     *
420
     * @return void
421
     *
422
     * @throws InvalidUriComponentException
423
     *         If the scheme isn't valid.
424
     */
425 117
    final protected function setScheme($scheme): void
426
    {
427 117
        $component = new Scheme($scheme);
428
429 104
        $this->scheme = $component->getValue();
430
    }
431
432
    /**
433
     * Sets the given user information to the URI
434
     *
435
     * @param mixed $user
436
     * @param mixed $password
437
     *
438
     * @return void
439
     *
440
     * @throws InvalidUriComponentException
441
     *         If the user information isn't valid.
442
     */
443 61
    final protected function setUserInfo($user, $password): void
444
    {
445 61
        $component = new UserInfo($user, $password);
446
447 44
        $this->userInfo = $component->getValue();
448
    }
449
450
    /**
451
     * Sets the given host to the URI
452
     *
453
     * @param mixed $host
454
     *
455
     * @return void
456
     *
457
     * @throws InvalidUriComponentException
458
     *         If the host isn't valid.
459
     */
460 128
    final protected function setHost($host): void
461
    {
462 128
        $component = new Host($host);
463
464 119
        $this->host = $component->getValue();
465
    }
466
467
    /**
468
     * Sets the given port to the URI
469
     *
470
     * @param mixed $port
471
     *
472
     * @return void
473
     *
474
     * @throws InvalidUriComponentException
475
     *         If the port isn't valid.
476
     */
477 57
    final protected function setPort($port): void
478
    {
479 57
        $component = new Port($port);
480
481 49
        $this->port = $component->getValue();
482
    }
483
484
    /**
485
     * Sets the given path to the URI
486
     *
487
     * @param mixed $path
488
     *
489
     * @return void
490
     *
491
     * @throws InvalidUriComponentException
492
     *         If the path isn't valid.
493
     */
494 502
    final protected function setPath($path): void
495
    {
496 502
        $component = new Path($path);
497
498 493
        $this->path = $component->getValue();
499
    }
500
501
    /**
502
     * Sets the given query to the URI
503
     *
504
     * @param mixed $query
505
     *
506
     * @return void
507
     *
508
     * @throws InvalidUriComponentException
509
     *         If the query isn't valid.
510
     */
511 57
    final protected function setQuery($query): void
512
    {
513 57
        $component = new Query($query);
514
515 48
        $this->query = $component->getValue();
516
    }
517
518
    /**
519
     * Sets the given fragment to the URI
520
     *
521
     * @param mixed $fragment
522
     *
523
     * @return void
524
     *
525
     * @throws InvalidUriComponentException
526
     *         If the fragment isn't valid.
527
     */
528 50
    final protected function setFragment($fragment): void
529
    {
530 50
        $component = new Fragment($fragment);
531
532 41
        $this->fragment = $component->getValue();
533
    }
534
}
535