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 DNProject 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 DNProject, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
14 | class DNProject extends DataObject { |
||
15 | |||
16 | /** |
||
17 | * @var array |
||
18 | */ |
||
19 | public static $db = array( |
||
20 | "Name" => "Varchar", |
||
21 | "CVSPath" => "Varchar(255)", |
||
22 | "DiskQuotaMB" => "Int", |
||
23 | "AllowedEnvironmentType" => "Varchar(255)", |
||
24 | "Client" => "Varchar(255)", |
||
25 | ); |
||
26 | |||
27 | /** |
||
28 | * @var array |
||
29 | */ |
||
30 | public static $has_many = array( |
||
31 | "Environments" => "DNEnvironment", |
||
32 | "CreateEnvironments" => "DNCreateEnvironment" |
||
33 | ); |
||
34 | |||
35 | /** |
||
36 | * @var array |
||
37 | */ |
||
38 | public static $many_many = array( |
||
39 | "Viewers" => "Group", |
||
40 | 'StarredBy' => "Member" |
||
41 | ); |
||
42 | |||
43 | /** |
||
44 | * @var array |
||
45 | */ |
||
46 | public static $summary_fields = array( |
||
47 | "Name", |
||
48 | "ViewersList", |
||
49 | ); |
||
50 | |||
51 | /** |
||
52 | * @var array |
||
53 | */ |
||
54 | public static $searchable_fields = array( |
||
55 | "Name", |
||
56 | ); |
||
57 | |||
58 | /** |
||
59 | * @var string |
||
60 | */ |
||
61 | private static $singular_name = 'Project'; |
||
62 | |||
63 | /** |
||
64 | * @var string |
||
65 | */ |
||
66 | private static $plural_name = 'Projects'; |
||
67 | |||
68 | /** |
||
69 | * @var string |
||
70 | */ |
||
71 | private static $default_sort = 'Name'; |
||
72 | |||
73 | /** |
||
74 | * Display the repository URL on the project page. |
||
75 | * |
||
76 | * @var bool |
||
77 | */ |
||
78 | private static $show_repository_url = false; |
||
79 | |||
80 | /** |
||
81 | * In-memory cache for currentBuilds per environment since fetching them from |
||
82 | * disk is pretty resource hungry. |
||
83 | * |
||
84 | * @var array |
||
85 | */ |
||
86 | protected static $relation_cache = array(); |
||
87 | |||
88 | /** |
||
89 | * @var bool|Member |
||
90 | */ |
||
91 | protected static $_current_member_cache = null; |
||
92 | |||
93 | /** |
||
94 | * Used by the sync task |
||
95 | * |
||
96 | * @param string $path |
||
97 | * @return \DNProject |
||
98 | */ |
||
99 | public static function create_from_path($path) { |
||
111 | |||
112 | /** |
||
113 | * Return the used quota in MB. |
||
114 | * |
||
115 | * @param int $round Number of decimal places to round to |
||
116 | * @return double The used quota size in MB |
||
117 | */ |
||
118 | public function getUsedQuotaMB($round = 2) { |
||
130 | |||
131 | /** |
||
132 | * Getter for DiskQuotaMB field to provide a default for existing |
||
133 | * records that have no quota field set, as it will need to default |
||
134 | * to a globally set size. |
||
135 | * |
||
136 | * @return string|int The quota size in MB |
||
137 | */ |
||
138 | public function getDiskQuotaMB() { |
||
148 | |||
149 | /** |
||
150 | * Has the disk quota been exceeded? |
||
151 | * |
||
152 | * @return boolean |
||
153 | */ |
||
154 | public function HasExceededDiskQuota() { |
||
157 | |||
158 | /** |
||
159 | * Is there a disk quota set for this project? |
||
160 | * |
||
161 | * @return boolean |
||
162 | */ |
||
163 | public function HasDiskQuota() { |
||
166 | |||
167 | /** |
||
168 | * Returns the current disk quota usage as a percentage |
||
169 | * |
||
170 | * @return int |
||
171 | */ |
||
172 | public function DiskQuotaUsagePercent() { |
||
179 | |||
180 | /** |
||
181 | * Get the menu to be shown on projects |
||
182 | * |
||
183 | * @return ArrayList |
||
184 | */ |
||
185 | View Code Duplication | public function Menu() { |
|
204 | |||
205 | /** |
||
206 | * Is this project currently at the root level of the controller that handles it? |
||
207 | * |
||
208 | * @return bool |
||
209 | */ |
||
210 | public function isCurrent() { |
||
213 | |||
214 | /** |
||
215 | * Return the current object from $this->Menu() |
||
216 | * Good for making titles and things |
||
217 | * |
||
218 | * @return DataObject |
||
219 | */ |
||
220 | public function CurrentMenu() { |
||
223 | |||
224 | /** |
||
225 | * Is this project currently in a controller that is handling it or performing a sub-task? |
||
226 | * |
||
227 | * @return bool |
||
228 | */ |
||
229 | public function isSection() { |
||
234 | |||
235 | /** |
||
236 | * Restrict access to viewing this project |
||
237 | * |
||
238 | * @param Member|null $member |
||
239 | * @return boolean |
||
240 | */ |
||
241 | public function canView($member = null) { |
||
252 | |||
253 | /** |
||
254 | * @param Member|null $member |
||
255 | * |
||
256 | * @return bool |
||
257 | */ |
||
258 | View Code Duplication | public function canRestore($member = null) { |
|
273 | |||
274 | /** |
||
275 | * @param Member|null $member |
||
276 | * @return bool |
||
277 | */ |
||
278 | View Code Duplication | public function canBackup($member = null) { |
|
293 | |||
294 | /** |
||
295 | * @param Member|null $member |
||
296 | * @return bool |
||
297 | */ |
||
298 | View Code Duplication | public function canUploadArchive($member = null) { |
|
313 | |||
314 | /** |
||
315 | * @param Member|null $member |
||
316 | * @return bool |
||
317 | */ |
||
318 | View Code Duplication | public function canDownloadArchive($member = null) { |
|
333 | |||
334 | /** |
||
335 | * This is a permission check for the front-end only. |
||
336 | * |
||
337 | * Only admins can create environments for now. Also, we need to check the value |
||
338 | * of AllowedEnvironmentType which dictates which backend to use to render the form. |
||
339 | * |
||
340 | * @param Member|null $member |
||
341 | * |
||
342 | * @return bool |
||
343 | */ |
||
344 | public function canCreateEnvironments($member = null) { |
||
354 | |||
355 | /** |
||
356 | * @return DataList |
||
357 | */ |
||
358 | public function DataArchives() { |
||
362 | |||
363 | /** |
||
364 | * Return all archives which are "manual upload requests", |
||
365 | * meaning they don't have a file attached to them (yet). |
||
366 | * |
||
367 | * @return DataList |
||
368 | */ |
||
369 | public function PendingManualUploadDataArchives() { |
||
372 | |||
373 | /** |
||
374 | * Build an environment variable array to be used with this project. |
||
375 | * |
||
376 | * This is relevant if every project needs to use an individual SSH pubkey. |
||
377 | * |
||
378 | * Include this with all Gitonomy\Git\Repository, and |
||
379 | * \Symfony\Component\Process\Processes. |
||
380 | * |
||
381 | * @return array |
||
382 | */ |
||
383 | public function getProcessEnv() { |
||
397 | |||
398 | /** |
||
399 | * Get a string of people allowed to view this project |
||
400 | * |
||
401 | * @return string |
||
402 | */ |
||
403 | public function getViewersList() { |
||
406 | |||
407 | /** |
||
408 | * @return DNData |
||
409 | */ |
||
410 | public function DNData() { |
||
413 | |||
414 | /** |
||
415 | * Provides a DNBuildList of builds found in this project. |
||
416 | * |
||
417 | * @return DNReferenceList |
||
418 | */ |
||
419 | public function DNBuildList() { |
||
422 | |||
423 | /** |
||
424 | * Provides a list of the branches in this project. |
||
425 | * |
||
426 | * @return DNBranchList |
||
427 | */ |
||
428 | public function DNBranchList() { |
||
434 | |||
435 | /** |
||
436 | * Provides a list of the tags in this project. |
||
437 | * |
||
438 | * @return DNReferenceList |
||
439 | */ |
||
440 | public function DNTagList() { |
||
446 | |||
447 | /** |
||
448 | * @return false|Gitonomy\Git\Repository |
||
449 | */ |
||
450 | public function getRepository() { |
||
457 | |||
458 | /** |
||
459 | * Provides a list of environments found in this project. |
||
460 | * CAUTION: filterByCallback will change this into an ArrayList! |
||
461 | * |
||
462 | * @return ArrayList |
||
463 | */ |
||
464 | public function DNEnvironmentList() { |
||
480 | |||
481 | /** |
||
482 | * @param string $usage |
||
483 | * @return ArrayList |
||
484 | */ |
||
485 | public function EnvironmentsByUsage($usage) { |
||
488 | |||
489 | /** |
||
490 | * Returns a map of envrionment name to build name |
||
491 | * |
||
492 | * @return false|DNDeployment |
||
493 | */ |
||
494 | public function currentBuilds() { |
||
504 | |||
505 | /** |
||
506 | * @param string |
||
507 | * @return string |
||
508 | */ |
||
509 | public function Link($action = '') { |
||
512 | |||
513 | /** |
||
514 | * @return string|null |
||
515 | */ |
||
516 | public function CreateEnvironmentLink() { |
||
522 | |||
523 | /** |
||
524 | * @return string |
||
525 | */ |
||
526 | public function ToggleStarLink() { |
||
529 | |||
530 | /** |
||
531 | * @return bool |
||
532 | */ |
||
533 | public function IsStarred() { |
||
544 | |||
545 | /** |
||
546 | * @param string $action |
||
547 | * @return string |
||
548 | */ |
||
549 | public function APILink($action) { |
||
552 | |||
553 | /** |
||
554 | * @return FieldList |
||
555 | */ |
||
556 | public function getCMSFields() { |
||
611 | |||
612 | /** |
||
613 | * If there isn't a capistrano env project folder, show options to create one |
||
614 | * |
||
615 | * @param FieldList $fields |
||
616 | */ |
||
617 | public function setCreateProjectFolderField(&$fields) { |
||
634 | |||
635 | /** |
||
636 | * @return boolean |
||
637 | */ |
||
638 | public function projectFolderExists() { |
||
644 | |||
645 | /** |
||
646 | * @return bool |
||
647 | */ |
||
648 | public function repoExists() { |
||
651 | |||
652 | /** |
||
653 | * Setup a asyncronous resque job to clone a git repository |
||
654 | */ |
||
655 | public function cloneRepo() { |
||
662 | |||
663 | /** |
||
664 | * @return string |
||
665 | */ |
||
666 | public function getLocalCVSPath() { |
||
669 | |||
670 | /** |
||
671 | * Checks for missing folders folder and schedules a git clone if the necessary |
||
672 | */ |
||
673 | public function onBeforeWrite() { |
||
679 | |||
680 | /** |
||
681 | * Ensure the path for this project has been created |
||
682 | */ |
||
683 | protected function checkProjectPath() { |
||
689 | |||
690 | /** |
||
691 | * Check if the CVSPath has been changed, and if so, ensure the repository has been updated |
||
692 | */ |
||
693 | protected function checkCVSPath() { |
||
702 | |||
703 | /** |
||
704 | * Delete related environments and folders |
||
705 | */ |
||
706 | public function onAfterDelete() { |
||
722 | |||
723 | /** |
||
724 | * Fetch the public key for this project. |
||
725 | * |
||
726 | * @return string|void |
||
727 | */ |
||
728 | public function getPublicKey() { |
||
735 | |||
736 | /** |
||
737 | * This returns that path of the public key if a key directory is set. It doesn't check whether the file exists. |
||
738 | * |
||
739 | * @return string|null |
||
740 | */ |
||
741 | public function getPublicKeyPath() { |
||
747 | |||
748 | /** |
||
749 | * This returns that path of the private key if a key directory is set. It doesn't check whether the file exists. |
||
750 | * |
||
751 | * @return string|null |
||
752 | */ |
||
753 | public function getPrivateKeyPath() { |
||
762 | |||
763 | /** |
||
764 | * Returns the location of the projects key dir if one exists. |
||
765 | * |
||
766 | * @return string|null |
||
767 | */ |
||
768 | public function getKeyDir() { |
||
779 | |||
780 | /** |
||
781 | * Setup a gridfield for the environment configs |
||
782 | * |
||
783 | * @param FieldList $fields |
||
784 | * @param GridField $environments |
||
785 | */ |
||
786 | protected function setEnvironmentFields(&$fields, $environments) { |
||
804 | |||
805 | /** |
||
806 | * Provide current repository URL to the users. |
||
807 | * |
||
808 | * @return void|string |
||
809 | */ |
||
810 | public function getRepositoryURL() { |
||
816 | |||
817 | /** |
||
818 | * Whitelist configuration that describes how to convert a repository URL into a link |
||
819 | * to a web user interface for that URL |
||
820 | * |
||
821 | * Consists of a hash of "full.lower.case.domain" => {configuration} key/value pairs |
||
822 | * |
||
823 | * {configuration} can either be boolean true to auto-detect both the host and the |
||
824 | * name of the UI provider, or a nested array that overrides either one or both |
||
825 | * of the auto-detected valyes |
||
826 | * |
||
827 | * @var array |
||
828 | */ |
||
829 | static private $repository_interfaces = array( |
||
830 | 'github.com' => array( |
||
831 | 'icon' => 'deploynaut/img/github.png' |
||
832 | ), |
||
833 | 'bitbucket.org' => array( |
||
834 | 'commit' => 'commits' |
||
835 | ), |
||
836 | 'repo.or.cz' => array( |
||
837 | 'scheme' => 'http', |
||
838 | 'name' => 'repo.or.cz', |
||
839 | 'regex' => array('^(.*)$' => '/w$1') |
||
840 | ), |
||
841 | |||
842 | /* Example for adding your own gitlab repository and override all auto-detected values (with their defaults) |
||
843 | 'gitlab.mysite.com' => array( |
||
844 | 'icon' => 'deploynaut/img/git.png', |
||
845 | 'host' => 'gitlab.mysite.com', |
||
846 | 'name' => 'Gitlab', |
||
847 | 'regex' => array('.git$' => ''), |
||
848 | 'commit' => "commit" |
||
849 | ), |
||
850 | */ |
||
851 | ); |
||
852 | |||
853 | /** |
||
854 | * Get a ViewableData structure describing the UI tool that lets the user view the repository code |
||
855 | * |
||
856 | * @return ArrayData |
||
857 | */ |
||
858 | public function getRepositoryInterface() { |
||
898 | |||
899 | /** |
||
900 | * @return string |
||
901 | */ |
||
902 | protected function getProjectFolderPath() { |
||
905 | |||
906 | /** |
||
907 | * Convenience wrapper for a single permission code. |
||
908 | * |
||
909 | * @param string $code |
||
910 | * @return SS_List |
||
911 | */ |
||
912 | public function whoIsAllowed($code) { |
||
915 | |||
916 | /** |
||
917 | * List members who have $codes on this project. |
||
918 | * Does not support Permission::DENY_PERMISSION malarky, same as Permission::get_groups_by_permission anyway... |
||
919 | * |
||
920 | * @param array|string $codes |
||
921 | * @return SS_List |
||
922 | */ |
||
923 | public function whoIsAllowedAny($codes) { |
||
940 | |||
941 | /** |
||
942 | * Convenience wrapper for a single permission code. |
||
943 | * |
||
944 | * @param string $code |
||
945 | * @param Member|null $member |
||
946 | * |
||
947 | * @return bool |
||
948 | */ |
||
949 | public function allowed($code, $member = null) { |
||
952 | |||
953 | /** |
||
954 | * Check if member has a permission code in this project. |
||
955 | * |
||
956 | * @param string $code |
||
957 | * @param Member|null $member |
||
958 | * |
||
959 | * @return bool |
||
960 | */ |
||
961 | public function allowedAny($codes, $member = null) { |
||
971 | |||
972 | /** |
||
973 | * @return ValidationResult |
||
974 | */ |
||
975 | protected function validate() { |
||
996 | |||
997 | } |
||
998 | |||
999 |
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.