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 Map 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 Map, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
14 | final class Map implements \IteratorAggregate, \ArrayAccess, Collection |
||
15 | { |
||
16 | use Traits\Collection; |
||
17 | use Traits\SquaredCapacity; |
||
18 | |||
19 | const MIN_CAPACITY = 8; |
||
20 | |||
21 | /** |
||
22 | * @var Pair[] |
||
23 | */ |
||
24 | private $internal = []; |
||
25 | |||
26 | /** |
||
27 | * Creates an instance using the values of an array or Traversable object. |
||
28 | * |
||
29 | * @param array|\Traversable|null $values |
||
30 | */ |
||
31 | 1024 | public function __construct($values = null) |
|
37 | |||
38 | /** |
||
39 | * Updates all values by applying a callback function to each value. |
||
40 | * |
||
41 | * @param callable $callback Accepts two arguments: key and value, should |
||
42 | * return what the updated value will be. |
||
43 | */ |
||
44 | 5 | public function apply(callable $callback) |
|
50 | |||
51 | /** |
||
52 | * @inheritDoc |
||
53 | */ |
||
54 | 4 | public function clear() |
|
59 | |||
60 | /** |
||
61 | * Return the first Pair from the Map |
||
62 | * |
||
63 | * @return Pair |
||
64 | * |
||
65 | * @throws UnderflowException |
||
66 | */ |
||
67 | 8 | public function first(): Pair |
|
75 | |||
76 | /** |
||
77 | * Return the last Pair from the Map |
||
78 | * |
||
79 | * @return Pair |
||
80 | * |
||
81 | * @throws UnderflowException |
||
82 | */ |
||
83 | 8 | public function last(): Pair |
|
91 | |||
92 | /** |
||
93 | * Return the pair at a specified position in the Map |
||
94 | * |
||
95 | * @param int $position |
||
96 | * |
||
97 | * @return Pair |
||
98 | * |
||
99 | * @throws OutOfRangeException |
||
100 | */ |
||
101 | 37 | public function skip(int $position): Pair |
|
109 | |||
110 | /** |
||
111 | * Merge an array of values with the current Map |
||
112 | * |
||
113 | * @param array|\Traversable $values |
||
114 | * |
||
115 | * @return Map |
||
116 | */ |
||
117 | 44 | public function merge($values): Map |
|
124 | |||
125 | /** |
||
126 | * Intersect |
||
127 | * |
||
128 | * @param Map $map |
||
129 | * |
||
130 | * @return Map |
||
131 | */ |
||
132 | 24 | public function intersect(Map $map): Map |
|
138 | |||
139 | /** |
||
140 | * Diff |
||
141 | * |
||
142 | * @param Map $map |
||
143 | * |
||
144 | * @return Map |
||
145 | */ |
||
146 | 24 | public function diff(Map $map): Map |
|
152 | |||
153 | /** |
||
154 | * Identical |
||
155 | * |
||
156 | * @param mixed $a |
||
157 | * @param mixed $b |
||
158 | * |
||
159 | * @return bool |
||
160 | */ |
||
161 | 625 | private function keysAreEqual($a, $b): bool |
|
169 | |||
170 | /** |
||
171 | * @param $key |
||
172 | * |
||
173 | * @return Pair|null |
||
174 | */ |
||
175 | 706 | private function lookupKey($key) |
|
183 | |||
184 | /** |
||
185 | * @param $value |
||
186 | * |
||
187 | * @return Pair|null |
||
188 | */ |
||
189 | 5 | private function lookupValue($value) |
|
197 | |||
198 | /** |
||
199 | * Returns whether an association a given key exists. |
||
200 | * |
||
201 | * @param mixed $key |
||
202 | * |
||
203 | * @return bool |
||
204 | */ |
||
205 | 69 | public function hasKey($key): bool |
|
209 | |||
210 | /** |
||
211 | * Returns whether an association for a given value exists. |
||
212 | * |
||
213 | * @param mixed $value |
||
214 | * |
||
215 | * @return bool |
||
216 | */ |
||
217 | 5 | public function hasValue($value): bool |
|
221 | |||
222 | /** |
||
223 | * @inheritDoc |
||
224 | */ |
||
225 | 715 | public function count(): int |
|
229 | |||
230 | /** |
||
231 | * Returns a new map containing only the values for which a predicate |
||
232 | * returns true. A boolean test will be used if a predicate is not provided. |
||
233 | * |
||
234 | * @param callable|null $callback Accepts a key and a value, and returns: |
||
235 | * true : include the value, |
||
236 | * false: skip the value. |
||
237 | * |
||
238 | * @return Map |
||
239 | */ |
||
240 | 76 | public function filter(callable $callback = null): Map |
|
252 | |||
253 | /** |
||
254 | * Returns the value associated with a key, or an optional default if the |
||
255 | * key is not associated with a value. |
||
256 | * |
||
257 | * @param mixed $key |
||
258 | * @param mixed $default |
||
259 | * |
||
260 | * @return mixed The associated value or fallback default if provided. |
||
261 | * |
||
262 | * @throws OutOfBoundsException if no default was provided and the key is |
||
263 | * not associated with a value. |
||
264 | */ |
||
265 | 33 | public function get($key, $default = null) |
|
277 | |||
278 | /** |
||
279 | * Returns a set of all the keys in the map. |
||
280 | * |
||
281 | * @return Set |
||
282 | */ |
||
283 | 37 | public function keys(): Set |
|
293 | |||
294 | /** |
||
295 | * Returns a new map using the results of applying a callback to each value. |
||
296 | * |
||
297 | * The keys will be equal in both maps. |
||
298 | * |
||
299 | * @param callable $callback Accepts two arguments: key and value, should |
||
300 | * return what the updated value will be. |
||
301 | * |
||
302 | * @return Map |
||
303 | */ |
||
304 | 5 | public function map(callable $callback): Map |
|
314 | |||
315 | /** |
||
316 | * Returns a sequence of pairs representing all associations. |
||
317 | * |
||
318 | * @return Sequence |
||
319 | */ |
||
320 | 7 | public function pairs(): Sequence |
|
330 | |||
331 | /** |
||
332 | * Associates a key with a value, replacing a previous association if there |
||
333 | * was one. |
||
334 | * |
||
335 | * @param mixed $key |
||
336 | * @param mixed $value |
||
337 | */ |
||
338 | 698 | public function put($key, $value) |
|
350 | |||
351 | /** |
||
352 | * Creates associations for all keys and corresponding values of either an |
||
353 | * array or iterable object. |
||
354 | * |
||
355 | * @param \Traversable|array $values |
||
356 | */ |
||
357 | 531 | public function putAll($values) |
|
363 | |||
364 | /** |
||
365 | * Iteratively reduces the map to a single value using a callback. |
||
366 | * |
||
367 | * @param callable $callback Accepts the carry, key, and value, and |
||
368 | * returns an updated carry value. |
||
369 | * |
||
370 | * @param mixed|null $initial Optional initial carry value. |
||
371 | * |
||
372 | * @return mixed The carry value of the final iteration, or the initial |
||
373 | * value if the map was empty. |
||
374 | */ |
||
375 | 8 | public function reduce(callable $callback, $initial = null) |
|
385 | |||
386 | /** |
||
387 | * |
||
388 | */ |
||
389 | 28 | private function delete(int $position) |
|
399 | |||
400 | /** |
||
401 | * Removes a key's association from the map and returns the associated value |
||
402 | * or a provided default if provided. |
||
403 | * |
||
404 | * @param mixed $key |
||
405 | * @param mixed $default |
||
406 | * |
||
407 | * @return mixed The associated value or fallback default if provided. |
||
408 | * |
||
409 | * @throws \OutOfBoundsException if no default was provided and the key is |
||
410 | * not associated with a value. |
||
411 | */ |
||
412 | 34 | public function remove($key, $default = null) |
|
427 | |||
428 | /** |
||
429 | * Returns a reversed copy of the map. |
||
430 | */ |
||
431 | 15 | public function reverse() |
|
435 | |||
436 | /** |
||
437 | * Returns a reversed copy of the map. |
||
438 | */ |
||
439 | 5 | public function reversed(): Map |
|
440 | { |
||
441 | 5 | $reversed = new self(); |
|
442 | 5 | $reversed->internal = array_reverse($this->internal); |
|
443 | |||
444 | 5 | return $reversed; |
|
445 | } |
||
446 | |||
447 | /** |
||
448 | * Returns a sub-sequence of a given length starting at a specified offset. |
||
449 | * |
||
450 | * @param int $offset If the offset is non-negative, the map will |
||
451 | * start at that offset in the map. If offset is |
||
452 | * negative, the map will start that far from the |
||
453 | * end. |
||
454 | * |
||
455 | * @param int|null $length If a length is given and is positive, the |
||
456 | * resulting set will have up to that many pairs in |
||
457 | * it. If the requested length results in an |
||
458 | * overflow, only pairs up to the end of the map |
||
459 | * will be included. |
||
460 | * |
||
461 | * If a length is given and is negative, the map |
||
462 | * will stop that many pairs from the end. |
||
463 | * |
||
464 | * If a length is not provided, the resulting map |
||
465 | * will contains all pairs between the offset and |
||
466 | * the end of the map. |
||
467 | * |
||
468 | * @return Map |
||
469 | */ |
||
470 | 400 | public function slice(int $offset, int $length = null): Map |
|
486 | |||
487 | /** |
||
488 | * Sorts the map in-place, based on an optional callable comparator. |
||
489 | * |
||
490 | * The map will be sorted by value. |
||
491 | * |
||
492 | * @param callable|null $comparator Accepts two values to be compared. |
||
493 | */ |
||
494 | 6 | View Code Duplication | public function sort(callable $comparator = null) |
507 | |||
508 | /** |
||
509 | * Returns a sorted copy of the map, based on an optional callable |
||
510 | * comparator. The map will be sorted by value. |
||
511 | * |
||
512 | * @param callable|null $comparator Accepts two values to be compared. |
||
513 | * |
||
514 | * @return Map |
||
515 | */ |
||
516 | 6 | View Code Duplication | public function sorted(callable $comparator = null): Map |
533 | |||
534 | /** |
||
535 | * Sorts the map in-place, based on an optional callable comparator. |
||
536 | * |
||
537 | * The map will be sorted by key. |
||
538 | * |
||
539 | * @param callable|null $comparator Accepts two keys to be compared. |
||
540 | */ |
||
541 | 10 | View Code Duplication | public function ksort(callable $comparator = null) |
554 | |||
555 | /** |
||
556 | * Returns a sorted copy of the map, based on an optional callable |
||
557 | * comparator. The map will be sorted by key. |
||
558 | * |
||
559 | * @param callable|null $comparator Accepts two keys to be compared. |
||
560 | * |
||
561 | * @return Map |
||
562 | */ |
||
563 | 6 | View Code Duplication | public function ksorted(callable $comparator = null): Map |
580 | |||
581 | /** |
||
582 | * Returns the sum of all values in the map. |
||
583 | * |
||
584 | * @return int|float The sum of all the values in the map. |
||
585 | */ |
||
586 | 7 | public function sum() |
|
590 | |||
591 | /** |
||
592 | * @inheritDoc |
||
593 | */ |
||
594 | 414 | public function toArray(): array |
|
604 | |||
605 | /** |
||
606 | * Returns a sequence of all the associated values in the Map. |
||
607 | * |
||
608 | * @return Sequence |
||
609 | */ |
||
610 | 10 | public function values(): Sequence |
|
620 | |||
621 | /** |
||
622 | * |
||
623 | * |
||
624 | * @return \Ds\Map |
||
625 | */ |
||
626 | 12 | public function union(Map $map): Map |
|
630 | |||
631 | /** |
||
632 | * XOR |
||
633 | * |
||
634 | * @param Map $map |
||
635 | * |
||
636 | * @return Map |
||
637 | */ |
||
638 | public function xor(Map $map): Map |
||
644 | |||
645 | /** |
||
646 | * Get iterator |
||
647 | */ |
||
648 | 535 | public function getIterator() |
|
654 | |||
655 | /** |
||
656 | * Debug Info |
||
657 | */ |
||
658 | 3 | public function __debugInfo() |
|
662 | |||
663 | /** |
||
664 | * @inheritdoc |
||
665 | */ |
||
666 | 8 | public function offsetSet($offset, $value) |
|
670 | |||
671 | /** |
||
672 | * @inheritdoc |
||
673 | * |
||
674 | * @throws OutOfBoundsException |
||
675 | */ |
||
676 | 18 | public function &offsetGet($offset) |
|
677 | { |
||
678 | 18 | $pair = $this->lookupKey($offset); |
|
679 | |||
680 | 18 | if ($pair) { |
|
681 | 17 | return $pair->value; |
|
682 | } |
||
683 | |||
684 | 1 | throw new OutOfBoundsException(); |
|
685 | } |
||
686 | |||
687 | /** |
||
688 | * @inheritdoc |
||
689 | */ |
||
690 | 4 | public function offsetUnset($offset) |
|
694 | |||
695 | /** |
||
696 | * @inheritdoc |
||
697 | */ |
||
698 | 20 | public function offsetExists($offset) |
|
702 | } |
||
703 |
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.