Completed
Push — master ( 16c8a5...965897 )
by Michael
02:58
created

URI   D

Complexity

Total Complexity 89

Size/Duplication

Total Lines 647
Duplicated Lines 6.8 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 89
c 1
b 0
f 1
lcom 1
cbo 0
dl 44
loc 647
ccs 205
cts 205
cp 1
rs 4.6794

38 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 28 2
A __toString() 0 4 1
F getUri() 44 117 36
A getScheme() 0 4 1
A setScheme() 0 6 1
A withScheme() 0 6 1
A getAuthority() 0 8 2
A getUserInfo() 0 10 2
A setUserInfo() 0 7 1
A withUserInfo() 0 6 1
A getHost() 0 4 1
A setHost() 0 6 1
A withHost() 0 6 1
A getPort() 0 4 1
A setPort() 0 10 4
A withPort() 0 6 1
A getPath() 0 10 4
B setPath() 0 26 6
A withPath() 0 6 1
A getSegments() 0 5 1
A getSegment() 0 6 2
A getDirectory() 0 4 1
A setDirectory() 0 6 1
A withDirectory() 0 6 1
A getFile() 0 4 1
A setFile() 0 6 1
A withFile() 0 6 1
A getQuery() 0 4 1
A setQuery() 0 8 1
A withQuery() 0 6 1
A getQueryValue() 0 4 2
A setQueryValue() 0 6 1
A withQueryValue() 0 6 1
A getFragment() 0 4 1
A setFragment() 0 6 1
A withFragment() 0 6 1
A decode() 0 4 1
A encode() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
/**
4
 * This file is part of the miBadger package.
5
 *
6
 * @author Michael Webbers <[email protected]>
7
 * @license http://opensource.org/licenses/Apache-2.0 Apache v2 License
8
 * @version 1.0.0
9
 */
10
11
namespace miBadger\Http;
12
13
use Psr\Http\Message\UriInterface;
14
15
/**
16
 * The URI class.
17
 *
18
 * @see http://tools.ietf.org/html/rfc3986
19
 * @since 1.0.0
20
 */
21
class URI implements UriInterface
22
{
23
	const DELIMITER_SCHEME = ':';
24
	const DELIMITER_AUTHORITY = '//';
25
	const DELIMITER_USER = '@';
26
	const DELIMITER_PASSWORD = ':';
27
	const DELIMITER_PORT = ':';
28
	const DELIMITER_PATH = '/';
29
	const DELIMITER_QUERY = '?';
30
	const DELIMITER_QUERY_PAIR = '&';
31
	const DELIMITER_QUERY_KEY_VALUE = '=';
32
	const DELIMITER_FRAGMENT = '#';
33
34
	const SCHEME = 'scheme';
35
	const AUTHORITY = 'authority';
36
	const USERNAME = 'user';
37
	const PASSWORD = 'pass';
38
	const HOST = 'host';
39
	const PORT = 'port';
40
	const PATH = 'path';
41
	const DIRECTORY = 'directory';
42
	const FILE = 'file';
43
	const QUERY = 'query';
44
	const FRAGMENT = 'fragment';
45
46
	/** @var string The scheme. */
47
	private $scheme;
48
49
	/** @var string The username. */
50
	private $username;
51
52
	/** @var string|null The password. */
53
	private $password;
54
55
	/** @var string The host. */
56
	private $host;
57
58
	/** @var int|null The port. */
59
	private $port;
60
61
	/** @var string The directory. */
62
	private $directory;
63
64
	/** @var string The file. */
65
	private $file;
66
67
	/** @var array The query. */
68
	private $query;
69
70
	/** @var string The fragment. */
71
	private $fragment;
72
73
	/**
74
	 * Construct a URI object with the given URI.
75
	 *
76
	 * @param string $uri
77
	 * @throws \UnexpectedValueException
78
	 */
79 51
	public function __construct($uri)
80
	{
81 51
		$component = parse_url($uri);
82
83 51
		if ($component === false) {
84 1
			throw new \UnexpectedValueException('Invalid uri');
85
		}
86
87
		// Replace with the null coalescing in PHP7. E.g. $component[scheme] ?? ''
88
		$component += [
89 51
			static::SCHEME => '',
90 51
			static::USERNAME => '',
91 51
			static::PASSWORD => null,
92 51
			static::HOST => '',
93 51
			static::PORT => null,
94 51
			static::PATH => '',
95 51
			static::QUERY => '',
96 51
			static::FRAGMENT => ''
97 51
		];
98
99 51
		$this->setScheme($component[static::SCHEME]);
100 51
		$this->setUserInfo($component[static::USERNAME], $component[static::PASSWORD]);
101 51
		$this->setHost($component[static::HOST]);
102 51
		$this->setPort($component[static::PORT]);
103 51
		$this->setPath($component[static::PATH]);
104 51
		$this->setQuery($component[static::QUERY]);
105 51
		$this->setFragment($component[static::FRAGMENT]);
106 51
	}
107
108
	/**
109
	 * Returns a string representation of the URI object.
110
	 *
111
	 * @return string a string representation of the URI object.
112
	 */
113 3
	public function __toString()
114
	{
115 3
		return $this->getUri();
116
	}
117
118
	/**
119
	 * Returns the URI with the given start and stop component.
120
	 *
121
	 * @param string $start = self::SCHEME
122
	 * @param string $end = self::FRAGMENT
123
	 * @return string the URI.
124
	 */
125 6
	public function getUri($start = self::SCHEME, $end = self::FRAGMENT)
126
	{
127 6
		$result = '';
128
129
		switch ($start) {
130 6
			default:
131 6
			case static::SCHEME:
132 5
				$scheme = $this->getScheme();
133
134 5
				if ($scheme) {
135 4
					$result .= $scheme . static::DELIMITER_SCHEME;
136 4
				}
137
138 5
				if ($end === static::SCHEME) {
139 1
					break;
140
				}
141
142
				// no break
143
144 6
			case static::AUTHORITY:
145 6 View Code Duplication
			case static::USERNAME:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146 6
				$username = $this->getUserInfo();
147
148 6
				if ($username && $this->getHost()) {
149 5
					$result .= static::DELIMITER_AUTHORITY . $username . static::DELIMITER_USER;
150 5
				}
151
152 6
				if ($end === static::USERNAME) {
153 1
					break;
154
				}
155
156
				// no break
157
158 6
			case static::HOST:
159 6
				$host = $this->getHost();
160
161 6
				if ($host && ($result === '' || !$this->getUserInfo())) {
162 1
					$result .= static::DELIMITER_AUTHORITY;
163 1
				}
164
165 6
				$result .= $host;
166
167 6
				if ($end === static::HOST) {
168 1
					break;
169
				}
170
171
				// no break
172
173 6 View Code Duplication
			case static::PORT:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
174 6
				$port = $this->getPort();
175
176 6
				if ($port !== null && $this->getHost()) {
177 5
					$result .= static::DELIMITER_PORT . $port;
178 5
				}
179
180 6
				if ($end === static::PORT || $end === static::AUTHORITY) {
181 2
					break;
182
				}
183
184
				// no break
185
186 5
			case static::PATH:
187 5 View Code Duplication
			case static::DIRECTORY:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
188 5
				$directory = $this->getDirectory();
189
190 5
				if ($result !== '' && $directory !== '' && substr($directory, 0, 1) !== static::DELIMITER_PATH) {
191 1
					$result .= static::DELIMITER_PATH;
192 1
				}
193
194 5
				$result .= $directory;
195
196 5
				if ($end === static::DIRECTORY) {
197 1
					break;
198
				}
199
200
				// no break
201
202 5 View Code Duplication
			case static::FILE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203 5
				$file = $this->getFile();
204
205 5
				if ($result !== '' && substr($result, -1) !== static::DELIMITER_PATH && $file !== '') {
206 1
					$result .= static::DELIMITER_PATH;
207 1
				}
208
209 5
				$result .= $this->getFile();
210
211 5
				if ($end === static::FILE || $end === static::PATH) {
212 1
					break;
213
				}
214
215
				// no break
216
217 5
			case static::QUERY:
218 5
				$query = $this->getQuery();
219
220 5
				if ($query) {
221 5
					$result .= static::DELIMITER_QUERY . $query;
222 5
				}
223
224 5
				if ($end === static::QUERY) {
225 1
					break;
226
				}
227
228
				// no break
229
230 4
			case static::FRAGMENT:
231 4
				$fragment = $this->getFragment();
232
233 4
				if ($fragment) {
234 3
					$result .= static::DELIMITER_FRAGMENT . $fragment;
235 3
				}
236
237
				// no break
238 4
		}
239
240 6
		return $result;
241
	}
242
243
	/**
244
	 * {@inheritdoc}
245
	 */
246 7
	public function getScheme()
247
	{
248 7
		return $this->scheme;
249
	}
250
251
	/**
252
	 * Set the scheme.
253
	 *
254
	 * @param string $scheme
255
	 * @return $this
256
	 */
257 51
	private function setScheme($scheme)
258
	{
259 51
		$this->scheme = strtolower($scheme);
260
261 51
		return $this;
262
	}
263
264
	/**
265
	 * {@inheritdoc}
266
	 */
267 1
	public function withScheme($scheme)
268
	{
269 1
		$result = clone $this;
270
271 1
		return $result->setScheme($scheme);
272
	}
273
274
	/**
275
	 * {@inheritdoc}
276
	 */
277 1
	public function getAuthority()
278
	{
279 1
		if (!$this->getHost()) {
280 1
			return '';
281
		}
282
283 1
		return substr($this->getUri(self::USERNAME, self::PORT), 2);
284
	}
285
286
	/**
287
	 * {@inheritdoc}
288
	 */
289 8
	public function getUserInfo()
290
	{
291 8
		$result = $this->username;
292
293 8
		if ($this->password !== null) {
294 7
			$result .= static::DELIMITER_PASSWORD . $this->password;
295 7
		}
296
297 8
		return $result;
298
	}
299
300
	/**
301
	 * Set the user info.
302
	 *
303
	 * @param string $username
304
	 * @param string|null $password = null
305
	 * @return $this
306
	 */
307 51
	private function setUserInfo($username, $password = null)
308
	{
309 51
		$this->username = $username;
310 51
		$this->password = $password;
311
312 51
		return $this;
313
	}
314
315
	/**
316
	 * {@inheritdoc}
317
	 */
318 1
	public function withUserInfo($username, $password = null)
319
	{
320 1
		$result = clone $this;
321
322 1
		return $result->setUserInfo($username, $password);
323
	}
324
325
	/**
326
	 * {@inheritdoc}
327
	 */
328 29
	public function getHost()
329
	{
330 29
		return $this->host;
331
	}
332
333
	/**
334
	 * Set the host.
335
	 *
336
	 * @param string $host
337
	 * @return $this
338
	 */
339 51
	private function setHost($host)
340
	{
341 51
		$this->host = strtolower($host);
342
343 51
		return $this;
344
	}
345
346
	/**
347
	 * {@inheritdoc}
348
	 */
349 2
	public function withHost($host)
350
	{
351 2
		$result = clone $this;
352
353 2
		return $result->setHost($host);
354
	}
355
356
	/**
357
	 * {@inheritdoc}
358
	 */
359 9
	public function getPort()
360
	{
361 9
		return $this->port;
362
	}
363
364
	/**
365
	 * Set the port.
366
	 *
367
	 * @param int|null $port
368
	 * @return $this
369
	 * @throws \InvalidArgumentException
370
	 */
371 51
	private function setPort($port = null)
372
	{
373 51
		if ($port !== null && (1 > $port || 0xffff < $port)) {
374 1
			throw new \InvalidArgumentException('Invalid port');
375
		}
376
377 51
		$this->port = $port;
378
379 51
		return $this;
380
	}
381
382
	/**
383
	 * {@inheritdoc}
384
	 */
385 2
	public function withPort($port)
386
	{
387 2
		$result = clone $this;
388
389 2
		return $result->setPort($port);
390
	}
391
392
	/**
393
	 * {@inheritdoc}
394
	 */
395 5
	public function getPath()
396
	{
397 5
		$result = $this->getDirectory();
398
399 5
		if ($result !== '' && substr($result, -1) !== static::DELIMITER_PATH && $this->getFile()) {
400 1
			$result .= static::DELIMITER_PATH;
401 1
		}
402
403 5
		return $result . $this->getFile();
404
	}
405
406
	/**
407
	 * Set the path.
408
	 *
409
	 * @param string $path
410
	 * @return $this
411
	 */
412 51
	private function setPath($path)
413
	{
414 51
		$directory = dirname($path);
415 51
		$file = basename($path);
416
417
		// If dirname is '.'. Then remove it.
418 51
		if ($directory === '.') {
419 1
			$directory = '';
420 1
		}
421
422
		// If the path ends with '/'. Then there is no file.
423 51
		if (substr($path, -1) === static::DELIMITER_PATH) {
424 4
			$directory = $path;
425 4
			$file = '';
426 4
		}
427
428
		// If the dirname and basename are both set. Then add the missing '/'.
429 51
		if (substr($directory, -1) !== static::DELIMITER_PATH && $directory !== '' && $file !== '') {
430 35
			$directory .= static::DELIMITER_PATH;
431 35
		}
432
433 51
		$this->setDirectory($directory);
434 51
		$this->setFile($file);
435
436 51
		return $this;
437
	}
438
439
	/**
440
	 * {@inheritdoc}
441
	 */
442 3
	public function withPath($path)
443
	{
444 3
		$result = clone $this;
445
446 3
		return $result->setPath($path);
447
	}
448
449
	/**
450
	 * Returns the URI segements
451
	 *
452
	 * @return string[] the URI segments
453
	 */
454 2
	public function getSegments()
455
	{
456
		// array_values reindexes the array and array_diff removes the empty elements.
457 2
		return array_values(array_diff(explode(static::DELIMITER_PATH, $this->getPath()), ['']));
458
	}
459
460
	/**
461
	 * Returns the segment at the given index or null if the segment at the given index doesn't exists.
462
	 *
463
	 * @param int $index
464
	 * @return string|null the segment at the given index or null if the segment at the given index doesn't exists
465
	 */
466 1
	public function getSegment($index)
467
	{
468 1
		$result = $this->getSegments();
469
470 1
		return isset($result[$index]) ? $result[$index] : null;
471
	}
472
473
	/**
474
	 * Returns the directory.
475
	 *
476
	 * @return string the directory.
477
	 */
478 11
	public function getDirectory()
479
	{
480 11
		return $this->directory;
481
	}
482
483
	/**
484
	 * Set the directory.
485
	 *
486
	 * @param string $directory
487
	 * @return $this
488
	 */
489 51
	private function setDirectory($directory)
490
	{
491 51
		$this->directory = $directory;
492
493 51
		return $this;
494
	}
495
496
	/**
497
	 * Return an instance with the specified directory.
498
	 *
499
	 * @param string $directory
500
	 * @return self
501
	 */
502 3
	public function withDirectory($directory)
503
	{
504 3
		$result = clone $this;
505
506 3
		return $result->setDirectory($directory);
507
	}
508
509
	/**
510
	 * Returns the file.
511
	 *
512
	 * @return string the file.
513
	 */
514 11
	public function getFile()
515
	{
516 11
		return $this->file;
517
	}
518
519
	/**
520
	 * Set the file.
521
	 *
522
	 * @param string $file
523
	 * @return $this
524
	 */
525 51
	private function setFile($file)
526
	{
527 51
		$this->file = $file;
528
529 51
		return $this;
530
	}
531
532
	/**
533
	 * Return an instance with the specified file.
534
	 *
535
	 * @param string $file
536
	 * @return self
537
	 */
538 1
	public function withFile($file)
539
	{
540 1
		$result = clone $this;
541
542 1
		return $result->setFile($file);
543
	}
544
545
	/**
546
	 * {@inheritdoc}
547
	 */
548 7
	public function getQuery()
549
	{
550 7
		return http_build_query($this->query);
551
	}
552
553
	/**
554
	 * Set the query.
555
	 *
556
	 * @param string $query
557
	 * @return $this
558
	 */
559 51
	private function setQuery($query)
560
	{
561 51
		$this->query = [];
562
563 51
		parse_str($query, $this->query);
564
565 51
		return $this;
566
	}
567
568
	/**
569
	 * {@inheritdoc}
570
	 */
571 1
	public function withQuery($query)
572
	{
573 1
		$result = clone $this;
574
575 1
		return $result->setQuery($query);
576
	}
577
578
	/**
579
	 * Returns the value to which the specified key is mapped, or null if the query map contains no mapping for the key.
580
	 *
581
	 * @param string $key
582
	 * @return string the value to which the specified key is mapped, or null if the query map contains no mapping for the key.
583
	 */
584 2
	public function getQueryValue($key)
585
	{
586 2
		return isset($this->query[$key]) ? $this->query[$key] : null;
587
	}
588
589
	/**
590
	 * Associates the specified value with the specified key in the query map.
591
	 *
592
	 * @param string $key
593
	 * @param string $value
594
	 * @return $this
595
	 */
596 1
	private function setQueryValue($key, $value)
597
	{
598 1
		$this->query[$key] = $value;
599
600 1
		return $this;
601
	}
602
603
	/**
604
	 * Return an instance with the specified query value.
605
	 *
606
	 * @param string $key
607
	 * @param string $value
608
	 * @return self
609
	 */
610 1
	public function withQueryValue($key, $value)
611
	{
612 1
		$result = clone $this;
613
614 1
		return $result->setQueryValue($key, $value);
615
	}
616
617
	/**
618
	 * {@inheritdoc}
619
	 */
620 6
	public function getFragment()
621
	{
622 6
		return $this->fragment;
623
	}
624
625
	/**
626
	 * Set the fragment.
627
	 *
628
	 * @param string $fragment
629
	 * @return $this
630
	 */
631 51
	private function setFragment($fragment)
632
	{
633 51
		$this->fragment = $fragment;
634
635 51
		return $this;
636
	}
637
638
	/**
639
	 * {@inheritdoc}
640
	 */
641 1
	public function withFragment($fragment)
642
	{
643 1
		$result = clone $this;
644
645 1
		return $result->setFragment($fragment);
646
	}
647
648
	/**
649
	 * Returns an instance with the decoded URI.
650
	 *
651
	 * @return self
652
	 */
653 1
	public function decode()
654
	{
655 1
		return new URI(html_entity_decode($this));
656
	}
657
658
	/**
659
	 * Returns an instance with the encoded URI.
660
	 *
661
	 * @return self
662
	 */
663 1
	public function encode()
664
	{
665 1
		return new URI(htmlentities($this));
666
	}
667
}
668