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 Traversable 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 Traversable, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
13 | class Traversable implements ITraversable, Interfaces\IOrderedTraversable |
||
14 | { |
||
15 | /** |
||
16 | * The iterator scheme used in the traversable instance. |
||
17 | * |
||
18 | * @var IIteratorScheme |
||
19 | */ |
||
20 | protected $scheme; |
||
21 | |||
22 | /** |
||
23 | * The source traversable. |
||
24 | * |
||
25 | * @var Traversable|null |
||
26 | */ |
||
27 | protected $source; |
||
28 | |||
29 | /** |
||
30 | * The element iterator for the traversable. |
||
31 | * |
||
32 | * @var \Traversable |
||
33 | */ |
||
34 | protected $elements; |
||
35 | |||
36 | public function __construct($elements = [], IIteratorScheme $scheme = null, Traversable $source = null) |
||
42 | |||
43 | /** |
||
44 | * Constructs a new traversable object from the supplied elements. |
||
45 | * |
||
46 | * @param array|\Traversable $elements |
||
47 | * @param IIteratorScheme|null $scheme |
||
48 | * @param Traversable|null $source |
||
49 | * |
||
50 | * @return ITraversable |
||
51 | */ |
||
52 | public static function from($elements, IIteratorScheme $scheme = null, Traversable $source = null) |
||
56 | |||
57 | /** |
||
58 | * Returns a callable for the traversable constructor. |
||
59 | * |
||
60 | * @param IIteratorScheme|null $scheme |
||
61 | * @param Traversable|null $source |
||
62 | * |
||
63 | * @return callable |
||
64 | */ |
||
65 | public static function factory(IIteratorScheme $scheme = null, Traversable $source = null) |
||
66 | { |
||
67 | //static:: doesn't work in closures? |
||
68 | $static = get_called_class(); |
||
69 | |||
70 | return function ($elements) use ($static, $scheme, $source) { |
||
71 | return $static::from($elements, $scheme, $source); |
||
72 | }; |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Returns a callable factory to construct an equivalent |
||
77 | * instance with the supplied elements. |
||
78 | * |
||
79 | * @return callable |
||
80 | */ |
||
81 | final protected function scopedSelfFactory() |
||
87 | |||
88 | /** |
||
89 | * Returns a new instance of the traversable with the scoped elements |
||
90 | * and same scheme and source. |
||
91 | * |
||
92 | * @param array|\Traversable $elements |
||
93 | * |
||
94 | * @return static |
||
95 | */ |
||
96 | protected function constructScopedSelf($elements) |
||
100 | |||
101 | public function isSource() |
||
105 | |||
106 | public function getSource() |
||
110 | |||
111 | public function asArray() |
||
115 | |||
116 | public function getIterator() |
||
120 | |||
121 | public function getTrueIterator() |
||
125 | |||
126 | public function getIteratorScheme() |
||
130 | |||
131 | public function asTraversable() |
||
135 | |||
136 | public function asCollection() |
||
140 | |||
141 | /** |
||
142 | * @return Iterators\IOrderedMap |
||
143 | */ |
||
144 | protected function asOrderedMap() |
||
149 | |||
150 | public function iterate(callable $function) |
||
154 | |||
155 | // <editor-fold defaultstate="collapsed" desc="Querying"> |
||
156 | |||
157 | public function first() |
||
165 | |||
166 | public function last() |
||
176 | |||
177 | public function where(callable $predicate) |
||
186 | |||
187 | public function orderByAscending(callable $function) |
||
197 | |||
198 | public function orderByDescending(callable $function) |
||
208 | |||
209 | public function orderBy(callable $function, $direction) |
||
214 | |||
215 | /** |
||
216 | * Verifies that the traversable is ordered. |
||
217 | * |
||
218 | * @param string $method The called method name |
||
219 | * |
||
220 | * @return Iterators\IOrderedIterator |
||
221 | * @throws PinqException |
||
222 | */ |
||
223 | private function validateIsOrdered($method) |
||
236 | |||
237 | public function thenBy(callable $function, $direction) |
||
246 | |||
247 | public function thenByAscending(callable $function) |
||
254 | |||
255 | public function thenByDescending(callable $function) |
||
262 | |||
263 | public function skip($amount) |
||
273 | |||
274 | public function take($amount) |
||
284 | |||
285 | public function slice($start, $amount) |
||
295 | |||
296 | public function indexBy(callable $function) |
||
308 | |||
309 | View Code Duplication | public function keys() |
|
323 | |||
324 | public function reindex() |
||
330 | |||
331 | public function groupBy(callable $function) |
||
341 | |||
342 | View Code Duplication | public function join($values) |
|
352 | |||
353 | View Code Duplication | public function groupJoin($values) |
|
364 | |||
365 | public function unique() |
||
369 | |||
370 | public function select(callable $function) |
||
380 | |||
381 | View Code Duplication | public function selectMany(callable $function) |
|
392 | |||
393 | // </editor-fold> |
||
394 | |||
395 | // <editor-fold defaultstate="collapsed" desc="Set Operations"> |
||
396 | |||
397 | public function union($values) |
||
406 | |||
407 | public function intersect($values) |
||
416 | |||
417 | public function difference($values) |
||
426 | |||
427 | // </editor-fold> |
||
428 | |||
429 | // <editor-fold defaultstate="collapsed" desc="Multiset Operations"> |
||
430 | |||
431 | public function append($values) |
||
440 | |||
441 | public function whereIn($values) |
||
450 | |||
451 | public function except($values) |
||
460 | |||
461 | // </editor-fold> |
||
462 | |||
463 | // <editor-fold defaultstate="collapsed" desc="Array Access"> |
||
464 | |||
465 | public function offsetExists($index) |
||
466 | { |
||
467 | foreach ($this->keys() as $key) { |
||
468 | if ($key === $index) { |
||
469 | return true; |
||
470 | } |
||
471 | } |
||
472 | |||
473 | return false; |
||
474 | } |
||
475 | |||
476 | public function offsetGet($index) |
||
477 | { |
||
478 | foreach ($this->select(function ($value, $key) { return [$key, $value]; }) as $element) { |
||
479 | if ($element[0] === $index) { |
||
480 | return $element[1]; |
||
481 | } |
||
482 | } |
||
483 | |||
484 | return false; |
||
485 | } |
||
486 | |||
487 | public function offsetSet($index, $value) |
||
491 | |||
492 | public function offsetUnset($index) |
||
493 | { |
||
494 | throw PinqException::notSupported(__METHOD__); |
||
495 | } |
||
496 | |||
497 | // </editor-fold> |
||
498 | |||
499 | // <editor-fold defaultstate="collapsed" desc="Aggregates"> |
||
500 | |||
501 | public function count() |
||
502 | { |
||
503 | if($this->elements instanceof \Countable) { |
||
504 | return $this->elements->count(); |
||
505 | } |
||
506 | |||
507 | $count = 0; |
||
508 | |||
509 | foreach($this->elements as $value) { |
||
510 | $count++; |
||
511 | } |
||
512 | |||
513 | return $count; |
||
514 | } |
||
515 | |||
516 | public function isEmpty() |
||
524 | |||
525 | public function contains($value) |
||
535 | |||
536 | public function aggregate(callable $function) |
||
553 | |||
554 | private function mapIterator(callable $function = null) |
||
555 | { |
||
556 | if ($function === null) { |
||
557 | return $this->elements; |
||
558 | } else { |
||
559 | return $this->scheme->projectionIterator( |
||
560 | $this->elements, |
||
561 | null, |
||
562 | $function |
||
563 | ); |
||
564 | } |
||
565 | } |
||
566 | |||
567 | View Code Duplication | public function maximum(callable $function = null) |
|
568 | { |
||
569 | $max = null; |
||
570 | |||
571 | foreach ($this->mapIterator($function) as $value) { |
||
572 | if ($value > $max) { |
||
573 | $max = $value; |
||
574 | } |
||
575 | } |
||
576 | |||
577 | return $max; |
||
578 | } |
||
579 | |||
580 | public function minimum(callable $function = null) |
||
581 | { |
||
582 | $min = null; |
||
583 | $first = true; |
||
584 | |||
585 | foreach ($this->mapIterator($function) as $value) { |
||
586 | if ($value < $min || $first) { |
||
587 | $min = $value; |
||
588 | $first = false; |
||
589 | } |
||
590 | } |
||
591 | |||
592 | return $min; |
||
593 | } |
||
594 | |||
595 | View Code Duplication | public function sum(callable $function = null) |
|
596 | { |
||
597 | $sum = null; |
||
598 | |||
599 | foreach ($this->mapIterator($function) as $value) { |
||
600 | $sum += $value; |
||
601 | } |
||
602 | |||
603 | return $sum; |
||
604 | } |
||
605 | |||
606 | public function average(callable $function = null) |
||
607 | { |
||
608 | $sum = null; |
||
609 | $count = 0; |
||
610 | |||
611 | foreach ($this->mapIterator($function) as $value) { |
||
612 | $sum += $value; |
||
613 | $count++; |
||
614 | } |
||
615 | |||
616 | return $count === 0 ? null : $sum / $count; |
||
617 | } |
||
618 | |||
619 | public function all(callable $function = null) |
||
629 | |||
630 | public function any(callable $function = null) |
||
631 | { |
||
632 | foreach ($this->mapIterator($function) as $value) { |
||
633 | if ($value) { |
||
634 | return true; |
||
635 | } |
||
636 | } |
||
637 | |||
638 | return false; |
||
639 | } |
||
640 | |||
641 | public function implode($delimiter, callable $function = null) |
||
651 | |||
652 | // </editor-fold> |
||
653 | } |
||
654 |
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.