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 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 |
||
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) |
|
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() |
|
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) |
|
242 | |||
243 | /** |
||
244 | * {@inheritdoc} |
||
245 | */ |
||
246 | 7 | public function getScheme() |
|
250 | |||
251 | /** |
||
252 | * Set the scheme. |
||
253 | * |
||
254 | * @param string $scheme |
||
255 | * @return $this |
||
256 | */ |
||
257 | 51 | private function setScheme($scheme) |
|
263 | |||
264 | /** |
||
265 | * {@inheritdoc} |
||
266 | */ |
||
267 | 1 | public function withScheme($scheme) |
|
273 | |||
274 | /** |
||
275 | * {@inheritdoc} |
||
276 | */ |
||
277 | 1 | public function getAuthority() |
|
285 | |||
286 | /** |
||
287 | * {@inheritdoc} |
||
288 | */ |
||
289 | 8 | public function getUserInfo() |
|
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) |
|
314 | |||
315 | /** |
||
316 | * {@inheritdoc} |
||
317 | */ |
||
318 | 1 | public function withUserInfo($username, $password = null) |
|
324 | |||
325 | /** |
||
326 | * {@inheritdoc} |
||
327 | */ |
||
328 | 29 | public function getHost() |
|
332 | |||
333 | /** |
||
334 | * Set the host. |
||
335 | * |
||
336 | * @param string $host |
||
337 | * @return $this |
||
338 | */ |
||
339 | 51 | private function setHost($host) |
|
345 | |||
346 | /** |
||
347 | * {@inheritdoc} |
||
348 | */ |
||
349 | 2 | public function withHost($host) |
|
355 | |||
356 | /** |
||
357 | * {@inheritdoc} |
||
358 | */ |
||
359 | 9 | public function getPort() |
|
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) |
|
381 | |||
382 | /** |
||
383 | * {@inheritdoc} |
||
384 | */ |
||
385 | 2 | public function withPort($port) |
|
391 | |||
392 | /** |
||
393 | * {@inheritdoc} |
||
394 | */ |
||
395 | 5 | public function getPath() |
|
405 | |||
406 | /** |
||
407 | * Set the path. |
||
408 | * |
||
409 | * @param string $path |
||
410 | * @return $this |
||
411 | */ |
||
412 | 51 | private function setPath($path) |
|
438 | |||
439 | /** |
||
440 | * {@inheritdoc} |
||
441 | */ |
||
442 | 3 | public function withPath($path) |
|
448 | |||
449 | /** |
||
450 | * Returns the URI segements |
||
451 | * |
||
452 | * @return string[] the URI segments |
||
453 | */ |
||
454 | 2 | public function getSegments() |
|
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) |
|
472 | |||
473 | /** |
||
474 | * Returns the directory. |
||
475 | * |
||
476 | * @return string the directory. |
||
477 | */ |
||
478 | 11 | public function getDirectory() |
|
482 | |||
483 | /** |
||
484 | * Set the directory. |
||
485 | * |
||
486 | * @param string $directory |
||
487 | * @return $this |
||
488 | */ |
||
489 | 51 | private function setDirectory($directory) |
|
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) |
|
508 | |||
509 | /** |
||
510 | * Returns the file. |
||
511 | * |
||
512 | * @return string the file. |
||
513 | */ |
||
514 | 11 | public function getFile() |
|
518 | |||
519 | /** |
||
520 | * Set the file. |
||
521 | * |
||
522 | * @param string $file |
||
523 | * @return $this |
||
524 | */ |
||
525 | 51 | private function setFile($file) |
|
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) |
|
544 | |||
545 | /** |
||
546 | * {@inheritdoc} |
||
547 | */ |
||
548 | 7 | public function getQuery() |
|
552 | |||
553 | /** |
||
554 | * Set the query. |
||
555 | * |
||
556 | * @param string $query |
||
557 | * @return $this |
||
558 | */ |
||
559 | 51 | private function setQuery($query) |
|
567 | |||
568 | /** |
||
569 | * {@inheritdoc} |
||
570 | */ |
||
571 | 1 | public function withQuery($query) |
|
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) |
|
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) |
|
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) |
|
616 | |||
617 | /** |
||
618 | * {@inheritdoc} |
||
619 | */ |
||
620 | 6 | public function getFragment() |
|
624 | |||
625 | /** |
||
626 | * Set the fragment. |
||
627 | * |
||
628 | * @param string $fragment |
||
629 | * @return $this |
||
630 | */ |
||
631 | 51 | private function setFragment($fragment) |
|
637 | |||
638 | /** |
||
639 | * {@inheritdoc} |
||
640 | */ |
||
641 | 1 | public function withFragment($fragment) |
|
647 | |||
648 | /** |
||
649 | * Returns an instance with the decoded URI. |
||
650 | * |
||
651 | * @return self |
||
652 | */ |
||
653 | 1 | public function decode() |
|
657 | |||
658 | /** |
||
659 | * Returns an instance with the encoded URI. |
||
660 | * |
||
661 | * @return self |
||
662 | */ |
||
663 | 1 | public function encode() |
|
667 | } |
||
668 |
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.