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 Agenda 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 Agenda, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
12 | class Agenda extends ContentType |
||
13 | { |
||
14 | const BASE_CACHE_TIME = 7200; // 2 hours |
||
15 | const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']; |
||
16 | const HOUR_MIN = 8; |
||
17 | const HOUR_MAX = 18; |
||
18 | |||
19 | public $html = '<div class="agenda">%data%</div>'; |
||
20 | public $css = <<<'EO1' |
||
21 | %field% .agenda { width: 100%; height: 100%; text-align: center; background-color: white; color: black; font-size: 1.1em; } |
||
22 | %field% .agenda-header { font-weight: bold; } |
||
23 | %field% .agenda-contents { width: 100%; height: calc(100% - 1.3em); display: table; table-layout: fixed; border-collapse: collapse; } |
||
24 | %field% .agenda-time { display: table-cell; width: 2.2em; border: solid 1px black; } |
||
25 | %field% .agenda-time-header { } |
||
26 | %field% .agenda-time-contents { width: 100%; height: calc(100% - 1.3em); display: table; position: relative; } |
||
27 | %field% .agenda-time-h { position: absolute; border-top: solid 1px black; width: 100%; } |
||
28 | %field% .agenda-time-m { position: absolute; border-top: dotted 1px black; right: 0; } |
||
29 | %field% .agenda-time-trace { position: absolute; border-top: dotted 1px gray; width: 100%; } |
||
30 | %field% .agenda-day { display: table-cell; border: solid 1px black; } |
||
31 | %field% .agenda-day-header { border-bottom: solid 1px black; } |
||
32 | %field% .agenda-day-contents { width: 100%; height: calc(100% - 1.3em); display: table; position: relative; } |
||
33 | %field% .agenda-event { position: absolute; overflow: hidden; border-bottom: solid 1px black; z-index: 10; } |
||
34 | %field% .agenda-event-desc { font-weight: bold; font-size: 1.1em; } |
||
35 | %field% .agenda-event-location { font-size: 1.1em; white-space: nowrap; } |
||
36 | %field% .agenda-event-name { word-break: break-all; display: block; } |
||
37 | EO1; |
||
38 | public $input = 'url'; |
||
39 | public $output = 'raw'; |
||
40 | public $usable = true; |
||
41 | public $exemple = '@web/images/agenda.preview.jpg'; |
||
42 | public $canPreview = true; |
||
43 | |||
44 | private static $translit; |
||
45 | private $color = []; |
||
46 | private $overlapScanOffset = 0.1; |
||
47 | private $tz; |
||
48 | |||
49 | /** |
||
50 | * {@inheritdoc} |
||
51 | */ |
||
52 | public function __construct($config = []) |
||
58 | |||
59 | /** |
||
60 | * {@inheritdoc} |
||
61 | */ |
||
62 | public function processData($data) |
||
82 | |||
83 | /** |
||
84 | * Extract data from ical event. |
||
85 | * |
||
86 | * @param array $e ical event |
||
87 | * |
||
88 | * @return array parsed event |
||
89 | */ |
||
90 | private function parseEvent($e) |
||
112 | |||
113 | /** |
||
114 | * Read .ical data and parse to day-based array. |
||
115 | * |
||
116 | * @param string $data ical raw data |
||
117 | * |
||
118 | * @return array agenda |
||
119 | */ |
||
120 | public function parseIcal($data) |
||
172 | |||
173 | /** |
||
174 | * Loop through day events and tag with overlaps. |
||
175 | * |
||
176 | * @param array $events |
||
177 | * @param int $from start hour |
||
178 | * @param int $to end hour |
||
179 | * |
||
180 | * @return array tagged events |
||
181 | */ |
||
182 | private function tagOverlaps($events, $from, $to) |
||
237 | |||
238 | /** |
||
239 | * Scan for overlap at precise time. |
||
240 | * |
||
241 | * @param array $events |
||
242 | * @param int $at scan hour |
||
243 | * |
||
244 | * @return array overlap |
||
245 | */ |
||
246 | private function scanOverlap($events, $at) |
||
257 | |||
258 | /** |
||
259 | * Loop through day events and scan for open position. |
||
260 | * |
||
261 | * @param array $events |
||
262 | * |
||
263 | * @return array positionned blocks |
||
264 | */ |
||
265 | private function positionBlocks($events) |
||
298 | |||
299 | /** |
||
300 | * Use agenda events data to build blocks for rendering. |
||
301 | * |
||
302 | * @param array $agenda |
||
303 | * |
||
304 | * @return array blocks |
||
305 | */ |
||
306 | public function blockize($agenda) |
||
323 | |||
324 | /** |
||
325 | * Render agenda left column with hours. |
||
326 | * |
||
327 | * @param array $agenda |
||
328 | * |
||
329 | * @return string HTML column |
||
330 | */ |
||
331 | private function renderHoursColumn($agenda) |
||
351 | |||
352 | /** |
||
353 | * Render agenda events blocks columns. |
||
354 | * |
||
355 | * @param array $agenda |
||
356 | * |
||
357 | * @return string HTML blocks columns |
||
358 | */ |
||
359 | private function renderEvents($agenda) |
||
417 | |||
418 | /** |
||
419 | * Render agenda to HTML. |
||
420 | * |
||
421 | * @param array $agenda |
||
422 | * |
||
423 | * @return string HTML result |
||
424 | */ |
||
425 | public function render($agenda) |
||
437 | |||
438 | /** |
||
439 | * Generate agenda HTML from .ical raw data. |
||
440 | * |
||
441 | * @param string $content ical raw data |
||
442 | * |
||
443 | * @return string|null HTML agenda |
||
444 | */ |
||
445 | public function genAgenda($content) |
||
456 | |||
457 | /** |
||
458 | * Apply self::filter() to each array member. |
||
459 | * |
||
460 | * @param array $arr input |
||
461 | * @param string $type array type |
||
462 | * |
||
463 | * @return array filtered output |
||
464 | */ |
||
465 | private static function arrayFilter(array $arr, $type) |
||
474 | |||
475 | /** |
||
476 | * Filter string from feed. |
||
477 | * |
||
478 | * @param string $str input string |
||
479 | * @param string $type string type |
||
480 | * |
||
481 | * @return string filtered string |
||
482 | */ |
||
483 | private static function filter($str, $type) |
||
526 | |||
527 | /** |
||
528 | * Generate color based on string |
||
529 | * Using MD5 to always get the same color for a given string. |
||
530 | * |
||
531 | * @param string $str |
||
532 | * |
||
533 | * @return string color hexcode |
||
534 | */ |
||
535 | private function getColor($str) |
||
552 | } |
||
553 |
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.