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 Builder 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 Builder, and based on these observations, apply Extract Interface, too.
1 | <?php namespace Myth\Docs; |
||
42 | class Builder implements DocBuilderInterface |
||
43 | { |
||
44 | |||
45 | protected $docs_ext = '.md'; |
||
46 | |||
47 | protected $ignore_files = ['_404.md']; |
||
48 | |||
49 | protected $doc_folders = []; |
||
50 | |||
51 | /** |
||
52 | * Stores the current folder alias, |
||
53 | * once the file has been found. |
||
54 | * |
||
55 | * @var null |
||
56 | */ |
||
57 | protected $current_folder = null; |
||
58 | |||
59 | protected $table_classes = 'table table-hover'; |
||
60 | |||
61 | protected $apppath = ''; |
||
62 | |||
63 | protected $formatters = []; |
||
64 | |||
65 | protected $page_title = null; |
||
66 | |||
67 | //-------------------------------------------------------------------- |
||
68 | |||
69 | public function __construct($config = array()) |
||
73 | |||
74 | //-------------------------------------------------------------------- |
||
75 | |||
76 | public function pageTitle() |
||
80 | |||
81 | //-------------------------------------------------------------------- |
||
82 | |||
83 | |||
84 | |||
85 | /** |
||
86 | * Does the actual work of reading in and parsing the help file. |
||
87 | * If a folder Nickname (see addDocFolder() ) is passed as the second parameter, |
||
88 | * it will limit it's search to that single folder. If nothing is passed, it will |
||
89 | * search through all of the folders in the order they were given to the library, |
||
90 | * until it finds the first one. |
||
91 | * |
||
92 | * @param string $path The 'path' of the file (relative to the docs |
||
93 | * folder. Usually from the URI) |
||
94 | * @param string $restrictToFolder (Optional) The folder nickname |
||
95 | * |
||
96 | * @return string |
||
97 | */ |
||
98 | public function readPage($path, $restrictToFolder = null) |
||
109 | |||
110 | //-------------------------------------------------------------------- |
||
111 | |||
112 | /** |
||
113 | * Parses the contents. Currently runs through the Markdown Extended |
||
114 | * parser to convert to HTML. |
||
115 | * |
||
116 | * @param $str |
||
117 | * @return mixed |
||
118 | */ |
||
119 | public function parse($str) |
||
123 | |||
124 | //-------------------------------------------------------------------- |
||
125 | |||
126 | /** |
||
127 | * Perform a few housekeeping tasks on a page, like rewriting URLs to full |
||
128 | * URLs, not relative, ensuring they link correctly, etc. |
||
129 | * |
||
130 | * @param $content |
||
131 | * @param null $site_url |
||
132 | * @param null $current_url |
||
133 | * @return string The post-processed HTML. |
||
134 | */ |
||
135 | public function postProcess($content, $site_url = null, $current_url = null) |
||
173 | //-------------------------------------------------------------------- |
||
174 | |||
175 | /** |
||
176 | * Allows users to define the classes that are attached to |
||
177 | * generated tables. |
||
178 | * |
||
179 | * @param null $classes |
||
180 | * @return $this |
||
181 | */ |
||
182 | public function setTableClasses($classes = null) |
||
188 | |||
189 | //-------------------------------------------------------------------- |
||
190 | |||
191 | /** |
||
192 | * Given the contents to render, will build a list of links for the sidebar |
||
193 | * out of the headings in the file. |
||
194 | * |
||
195 | * Note: Will ONLY use h2 and h3 to build the links from. |
||
196 | * |
||
197 | * Note: The $content passed in WILL be modified by adding named anchors |
||
198 | * that match up with the locations. |
||
199 | * |
||
200 | * @param string $content The HTML to analyse for headings. |
||
201 | * @return string |
||
202 | */ |
||
203 | public function buildDocumentMap(&$content) |
||
232 | |||
233 | //-------------------------------------------------------------------- |
||
234 | |||
235 | /** |
||
236 | * Stores the name of the callback method to run to convert the source |
||
237 | * files to viewable files. By default, this should be used to register |
||
238 | * a Mardown Extended formatter with the system, but could be used to |
||
239 | * extend the |
||
240 | * |
||
241 | * @param string $callback |
||
242 | * @param bool $cascade // If FALSE the formatting of a component ends here. If TRUE, will be passed to next formatter. |
||
243 | * @return $this |
||
244 | */ |
||
245 | public function registerFormatter($callback = null, $cascade = false) |
||
256 | |||
257 | //-------------------------------------------------------------------- |
||
258 | |||
259 | /** |
||
260 | * Runs the text through the registered formatters. |
||
261 | * |
||
262 | * @param $str |
||
263 | * @return mixed |
||
264 | */ |
||
265 | View Code Duplication | public function format($str) |
|
280 | |||
281 | //-------------------------------------------------------------------- |
||
282 | |||
283 | //-------------------------------------------------------------------- |
||
284 | // Table of Contents methods |
||
285 | //-------------------------------------------------------------------- |
||
286 | |||
287 | /** |
||
288 | * Retrieves the list of files in a folder and preps the name and filename |
||
289 | * so it's ready for creating the HTML. |
||
290 | * |
||
291 | * @param String $folder The path to the folder to retrieve. |
||
292 | * |
||
293 | * @return Array An associative array @see parse_ini_file for format |
||
294 | * details. |
||
295 | */ |
||
296 | public function buildTOC($folder) |
||
353 | |||
354 | //-------------------------------------------------------------------- |
||
355 | |||
356 | /** |
||
357 | * Sorts the passed TOC array into columns of as close to equal length |
||
358 | * as we can get it. |
||
359 | * |
||
360 | * @param $toc |
||
361 | * @return array |
||
362 | */ |
||
363 | protected function columnizeTOC($toc) |
||
406 | |||
407 | //-------------------------------------------------------------------- |
||
408 | |||
409 | //-------------------------------------------------------------------- |
||
410 | // Folder Methods |
||
411 | //-------------------------------------------------------------------- |
||
412 | |||
413 | /** |
||
414 | * Returns the current docFolders array. |
||
415 | * |
||
416 | * @return array |
||
417 | */ |
||
418 | public function docFolders() |
||
422 | |||
423 | //-------------------------------------------------------------------- |
||
424 | |||
425 | /** |
||
426 | * Registers a path to be used when searching for documentation files. |
||
427 | * |
||
428 | * @param $name A nickname to reference it by later. |
||
429 | * @param $path The server path to the folder. |
||
430 | * @return $this |
||
431 | */ |
||
432 | public function addDocFolder($name, $path) |
||
449 | |||
450 | //-------------------------------------------------------------------- |
||
451 | |||
452 | /** |
||
453 | * Removes a folder from the folders we scan for documentation files |
||
454 | * within. |
||
455 | * |
||
456 | * @param $name |
||
457 | * @return $this |
||
458 | */ |
||
459 | public function removeDocFolder($name) |
||
469 | |||
470 | //-------------------------------------------------------------------- |
||
471 | |||
472 | //-------------------------------------------------------------------- |
||
473 | // Private Methods |
||
474 | //-------------------------------------------------------------------- |
||
475 | |||
476 | /** |
||
477 | * Analyzes the passed in current url string and checks against |
||
478 | * a list of groups to determine what the current group is. |
||
479 | * |
||
480 | * @param $current_url |
||
481 | * @param $groups |
||
482 | * @return string |
||
483 | */ |
||
484 | protected function detectCurrentFolder($current_url, $groups = []) |
||
507 | |||
508 | //-------------------------------------------------------------------- |
||
509 | |||
510 | //-------------------------------------------------------------------- |
||
511 | // Private Methods |
||
512 | //-------------------------------------------------------------------- |
||
513 | |||
514 | /** |
||
515 | * Locates the file on disk and reads the contents into a single string. |
||
516 | * |
||
517 | * If a folder Nickname (see addDocFolder() ) is passed as the second parameter, |
||
518 | * it will limit it's search to that single folder. If nothing is passed, it will |
||
519 | * search through all of the folders in the order they were given to the library, |
||
520 | * until it finds the first one. |
||
521 | * |
||
522 | * @param string $path The 'path' of the file (relative to the docs |
||
523 | * folder. Usually from the URI) |
||
524 | * @param string $restrictToFolder (Optional) The nickname of one of the |
||
525 | * folders to restrict the search to. |
||
526 | * |
||
527 | * @throws RuntimeException |
||
528 | * @return null|string |
||
529 | */ |
||
530 | private function locateAndReadFile($path, $restrictToFolder = null) |
||
554 | |||
555 | //-------------------------------------------------------------------- |
||
556 | |||
557 | /** |
||
558 | * Re-formats the passed in link. |
||
559 | * |
||
560 | * @param $link |
||
561 | * @param $current_url |
||
562 | * @param $site_url |
||
563 | * @return mixed |
||
564 | */ |
||
565 | private function reformatAnchor($link, $groups, $current_url, $site_url) |
||
566 | { |
||
567 | // Grab the href value. |
||
568 | $href = $link->attributes()->href; |
||
569 | |||
570 | // If the href is null, it's probably a named anchor with no content. |
||
571 | if (! $href) { |
||
572 | // Make sure it has an href, else the XML will not close this |
||
573 | // tag correctly. |
||
574 | $link['href'] = ' '; |
||
575 | |||
576 | return $link; |
||
577 | } |
||
578 | |||
579 | // Remove any trailing # signs |
||
580 | $href = rtrim($href, '# '); |
||
581 | |||
582 | // If the href starts with #, then attach the current_url to it |
||
583 | if ($href != '' && substr_compare($href, '#', 0, 1) === 0) { |
||
584 | $link['href'] = $current_url . $href; |
||
585 | |||
586 | return $link; |
||
587 | } |
||
588 | |||
589 | // If it's a full external path, go on... |
||
590 | if ((strpos($href, 'http://') !== false || strpos($href, 'https://') !== false) && |
||
591 | strpos($href, $site_url) === false |
||
592 | ) { |
||
593 | $link['target'] = "_blank"; |
||
594 | return $link; |
||
595 | } |
||
596 | |||
597 | // If it's a full local path, get rid of it. |
||
598 | if ($site_url !== "/" && strpos($href, $site_url) !== false) { |
||
599 | $href = str_replace($site_url, '', $href); |
||
600 | } |
||
601 | |||
602 | // Strip out some unnecessary items, just in case they're there. |
||
603 | if (substr($href, 0, strlen('docs/')) == 'docs/') { |
||
604 | $href = substr($href, strlen('docs/')); |
||
605 | } |
||
606 | |||
607 | // This includes 'bonfire/' if it was missed during the conversion. |
||
608 | if (substr($href, 0, strlen('bonfire/')) == 'bonfire/') { |
||
609 | $href = substr($href, strlen('bonfire/')); |
||
610 | } |
||
611 | |||
612 | // If another 'group' is not already defined at the head of the link |
||
613 | // then add the current group to it. |
||
614 | $group_found = false; |
||
615 | |||
616 | foreach ($groups as $group) { |
||
617 | if (strpos($href, $group) === 0) { |
||
618 | $group_found = true; |
||
619 | } |
||
620 | } |
||
621 | |||
622 | if (! $group_found) { |
||
623 | $href = $this->current_folder . '/' . $href; |
||
624 | } |
||
625 | |||
626 | // Convert to full site_url |
||
627 | if (strpos($href, 'http') !== 0) { |
||
628 | $href = $site_url . 'docs/' . ltrim($href, '/ '); |
||
629 | } |
||
630 | |||
631 | // Save the corrected href |
||
632 | $link['href'] = $href; |
||
633 | |||
634 | return $link; |
||
635 | } |
||
636 | |||
637 | //-------------------------------------------------------------------- |
||
638 | |||
639 | /** |
||
640 | * Creates a Document Map based on <h2> and <h3> tags. |
||
641 | * Also adds named anchors into the $content so the map |
||
642 | * can link to the content properly. |
||
643 | * |
||
644 | * @param $content |
||
645 | * @param $xml |
||
646 | * @param $map |
||
647 | * @return array |
||
648 | */ |
||
649 | protected function extractDocMapAndAddAnchors(&$content, $xml, $map) |
||
720 | //-------------------------------------------------------------------- |
||
721 | |||
722 | /** |
||
723 | * Create a Directory Map |
||
724 | * |
||
725 | * Reads the specified directory and builds an array |
||
726 | * representation of it. Sub-folders contained with the |
||
727 | * directory will be mapped as well. |
||
728 | * |
||
729 | * @param string $source_dir Path to source |
||
730 | * @param int $directory_depth Depth of directories to traverse |
||
731 | * (0 = fully recursive, 1 = current dir, etc) |
||
732 | * @param bool $hidden Whether to show hidden files |
||
733 | * @return array |
||
734 | */ |
||
735 | View Code Duplication | protected function directory_map($source_dir, $directory_depth = 0, $hidden = FALSE) |
|
763 | |||
764 | //-------------------------------------------------------------------- |
||
765 | |||
766 | } |
||
767 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.