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 DNEnvironment 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 DNEnvironment, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
42 | class DNEnvironment extends DataObject { |
||
43 | |||
44 | /** |
||
45 | * If this is set to a full pathfile, it will be used as template |
||
46 | * file when creating a new capistrano environment config file. |
||
47 | * |
||
48 | * If not set, the default 'environment.template' from the module |
||
49 | * root is used |
||
50 | * |
||
51 | * @config |
||
52 | * @var string |
||
53 | */ |
||
54 | private static $template_file = ''; |
||
55 | |||
56 | /** |
||
57 | * Set this to true to allow editing of the environment files via the web admin |
||
58 | * |
||
59 | * @var bool |
||
60 | */ |
||
61 | private static $allow_web_editing = false; |
||
62 | |||
63 | /** |
||
64 | * @var array |
||
65 | */ |
||
66 | private static $casting = array( |
||
67 | 'DeployHistory' => 'Text' |
||
68 | ); |
||
69 | |||
70 | /** |
||
71 | * Allowed backends. A map of Injector identifier to human-readable label. |
||
72 | * |
||
73 | * @config |
||
74 | * @var array |
||
75 | */ |
||
76 | private static $allowed_backends = array(); |
||
77 | |||
78 | /** |
||
79 | * @var array |
||
80 | */ |
||
81 | public static $db = array( |
||
82 | "Filename" => "Varchar(255)", |
||
83 | "Name" => "Varchar(255)", |
||
84 | "URL" => "Varchar(255)", |
||
85 | "BackendIdentifier" => "Varchar(255)", // Injector identifier of the DeploymentBackend |
||
86 | "DryRunEnabled" => "Boolean", // True if the dry run button should be enabled on the frontend |
||
87 | "Usage" => "Enum('Production, UAT, Test, Unspecified', 'Unspecified')" |
||
88 | ); |
||
89 | |||
90 | /** |
||
91 | * @var array |
||
92 | */ |
||
93 | public static $has_one = array( |
||
94 | "Project" => "DNProject", |
||
95 | "CreateEnvironment" => "DNCreateEnvironment" |
||
96 | ); |
||
97 | |||
98 | /** |
||
99 | * @var array |
||
100 | */ |
||
101 | public static $has_many = array( |
||
102 | "Deployments" => "DNDeployment", |
||
103 | "DataArchives" => "DNDataArchive", |
||
104 | "Pipelines" => "Pipeline" // Only one Pipeline can be 'Running' at any one time. @see self::CurrentPipeline(). |
||
105 | ); |
||
106 | |||
107 | /** |
||
108 | * @var array |
||
109 | */ |
||
110 | public static $many_many = array( |
||
111 | "Viewers" => "Member", // Who can view this environment |
||
112 | "ViewerGroups" => "Group", |
||
113 | "Deployers" => "Member", // Who can deploy to this environment |
||
114 | "DeployerGroups" => "Group", |
||
115 | "CanRestoreMembers" => "Member", // Who can restore archive files to this environment |
||
116 | "CanRestoreGroups" => "Group", |
||
117 | "CanBackupMembers" => "Member", // Who can backup archive files from this environment |
||
118 | "CanBackupGroups" => "Group", |
||
119 | "ArchiveUploaders" => "Member", // Who can upload archive files linked to this environment |
||
120 | "ArchiveUploaderGroups" => "Group", |
||
121 | "ArchiveDownloaders" => "Member", // Who can download archive files from this environment |
||
122 | "ArchiveDownloaderGroups" => "Group", |
||
123 | "ArchiveDeleters" => "Member", // Who can delete archive files from this environment, |
||
124 | "ArchiveDeleterGroups" => "Group", |
||
125 | "PipelineApprovers" => "Member", // Who can approve / reject pipelines from this environment |
||
126 | "PipelineApproverGroups" => "Group", |
||
127 | "PipelineCancellers" => "Member", // Who can abort pipelines |
||
128 | "PipelineCancellerGroups" => "Group" |
||
129 | ); |
||
130 | |||
131 | /** |
||
132 | * @var array |
||
133 | */ |
||
134 | public static $summary_fields = array( |
||
135 | "Name" => "Environment Name", |
||
136 | "Usage" => "Usage", |
||
137 | "URL" => "URL", |
||
138 | "DeployersList" => "Can Deploy List", |
||
139 | "CanRestoreMembersList" => "Can Restore List", |
||
140 | "CanBackupMembersList" => "Can Backup List", |
||
141 | "ArchiveUploadersList" => "Can Upload List", |
||
142 | "ArchiveDownloadersList" => "Can Download List", |
||
143 | "ArchiveDeletersList" => "Can Delete List", |
||
144 | "PipelineApproversList" => "Can Approve List", |
||
145 | "PipelineCancellersList" => "Can Cancel List" |
||
146 | ); |
||
147 | |||
148 | private static $singular_name = 'Capistrano Environment'; |
||
149 | |||
150 | private static $plural_name = 'Capistrano Environments'; |
||
151 | |||
152 | /** |
||
153 | * @var array |
||
154 | */ |
||
155 | public static $searchable_fields = array( |
||
156 | "Name", |
||
157 | ); |
||
158 | |||
159 | /** |
||
160 | * @var string |
||
161 | */ |
||
162 | private static $default_sort = 'Name'; |
||
163 | |||
164 | /** |
||
165 | * Used by the sync task |
||
166 | * |
||
167 | * @param string $path |
||
168 | * @return \DNEnvironment |
||
169 | */ |
||
170 | public static function create_from_path($path) { |
||
171 | $e = DNEnvironment::create(); |
||
172 | $e->Filename = $path; |
||
173 | $e->Name = basename($e->Filename, '.rb'); |
||
174 | |||
175 | // add each administrator member as a deployer of the new environment |
||
176 | $adminGroup = Group::get()->filter('Code', 'administrators')->first(); |
||
177 | $e->DeployerGroups()->add($adminGroup); |
||
178 | return $e; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Get the deployment backend used for this environment. |
||
183 | * |
||
184 | * Enforces compliance with the allowed_backends setting; if the DNEnvironment.BackendIdentifier value is |
||
185 | * illegal then that value is ignored. |
||
186 | * |
||
187 | * @return DeploymentBackend |
||
188 | */ |
||
189 | public function Backend() { |
||
190 | $backends = array_keys($this->config()->get('allowed_backends', Config::FIRST_SET)); |
||
191 | switch(sizeof($backends)) { |
||
192 | // Nothing allowed, use the default value "DeploymentBackend" |
||
193 | case 0: |
||
194 | $backend = "DeploymentBackend"; |
||
195 | break; |
||
196 | |||
197 | // Only 1 thing allowed, use that |
||
198 | case 1: |
||
199 | $backend = $backends[0]; |
||
200 | break; |
||
201 | |||
202 | // Multiple choices, use our choice if it's legal, otherwise default to the first item on the list |
||
203 | default: |
||
204 | $backend = $this->BackendIdentifier; |
||
205 | if(!in_array($backend, $backends)) { |
||
206 | $backend = $backends[0]; |
||
207 | } |
||
208 | } |
||
209 | |||
210 | return Injector::inst()->get($backend); |
||
211 | } |
||
212 | |||
213 | public function Menu() { |
||
214 | $list = new ArrayList(); |
||
215 | |||
216 | $controller = Controller::curr(); |
||
217 | $actionType = $controller->getField('CurrentActionType'); |
||
218 | |||
219 | $list->push(new ArrayData(array( |
||
220 | 'Link' => sprintf('naut/project/%s/environment/%s', $this->Project()->Name, $this->Name), |
||
221 | 'Title' => 'Deployments', |
||
222 | 'IsCurrent' => $this->isCurrent(), |
||
223 | 'IsSection' => $this->isSection() && $actionType == DNRoot::ACTION_DEPLOY |
||
224 | ))); |
||
225 | |||
226 | $this->extend('updateMenu', $list); |
||
227 | |||
228 | return $list; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Return the current object from $this->Menu() |
||
233 | * Good for making titles and things |
||
234 | */ |
||
235 | public function CurrentMenu() { |
||
238 | |||
239 | /** |
||
240 | * Return a name for this environment. |
||
241 | * |
||
242 | * @param string $separator The string used when concatenating project with env name |
||
243 | * @return string |
||
244 | */ |
||
245 | public function getFullName($separator = ':') { |
||
248 | |||
249 | /** |
||
250 | * URL for the environment that can be used if no explicit URL is set. |
||
251 | */ |
||
252 | public function getDefaultURL() { |
||
255 | |||
256 | public function getBareURL() { |
||
262 | |||
263 | public function getBareDefaultURL() { |
||
269 | |||
270 | /** |
||
271 | * @return boolean true if there is a pipeline for the current environment. |
||
272 | */ |
||
273 | public function HasPipelineSupport() { |
||
277 | |||
278 | /** |
||
279 | * Returns a {@link Pipeline} object that is linked to this environment, but isn't saved into the database. This |
||
280 | * shouldn't be saved into the database unless you plan on starting an actual pipeline. |
||
281 | * |
||
282 | * @return Pipeline |
||
283 | */ |
||
284 | public function GenericPipeline() { |
||
289 | |||
290 | /** |
||
291 | * Returns the parsed config, based on a {@link Pipeline} being created for this {@link DNEnvironment}. |
||
292 | * |
||
293 | * @return ArrayData |
||
294 | */ |
||
295 | public function GenericPipelineConfig() { |
||
301 | |||
302 | /** |
||
303 | * Extract pipeline configuration data from the source yml file |
||
304 | * |
||
305 | * @return array |
||
306 | */ |
||
307 | public function loadPipelineConfig() { |
||
315 | |||
316 | /** |
||
317 | * Returns the {@link DNEnvironment} object relating to the pipeline config for this environment. The environment |
||
318 | * YAML file (e.g. project1-uat.yml; see docs/en/pipelines.md) contains two variable called `DependsOnProject` and |
||
319 | * `DependsOnEnvironment` - these are used together to find the {@link DNEnvironment} that this environment should |
||
320 | * rely on. |
||
321 | */ |
||
322 | public function DependsOnEnvironment() { |
||
330 | |||
331 | /** |
||
332 | * @return bool true if there is a currently running Pipeline, and false if there isn't |
||
333 | */ |
||
334 | public function HasCurrentPipeline() { |
||
337 | |||
338 | /** |
||
339 | * This can be used to determine if there is a currently running pipeline (there can only be one running per |
||
340 | * {@link DNEnvironment} at once), as well as getting the current pipeline to be shown in templates. |
||
341 | * |
||
342 | * @return DataObject|null The currently running pipeline, or null if there isn't any. |
||
343 | */ |
||
344 | public function CurrentPipeline() { |
||
347 | |||
348 | /** |
||
349 | * @return bool true if the current user can cancel a running pipeline |
||
350 | */ |
||
351 | public function CanCancelPipeline() { |
||
358 | |||
359 | /** |
||
360 | * Environments are only viewable by people that can view the environment. |
||
361 | * |
||
362 | * @param Member|null $member |
||
363 | * @return boolean |
||
364 | */ |
||
365 | public function canView($member = null) { |
||
386 | |||
387 | /** |
||
388 | * Allow deploy only to some people. |
||
389 | * |
||
390 | * @param Member|null $member |
||
391 | * @return boolean |
||
392 | */ |
||
393 | View Code Duplication | public function canDeploy($member = null) { |
|
411 | |||
412 | /** |
||
413 | * Provide reason why the user cannot deploy. |
||
414 | * |
||
415 | * @return string |
||
416 | */ |
||
417 | public function getCannotDeployMessage() { |
||
420 | |||
421 | /** |
||
422 | * Allows only selected {@link Member} objects to restore {@link DNDataArchive} objects into this |
||
423 | * {@link DNEnvironment}. |
||
424 | * |
||
425 | * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember(); |
||
426 | * @return boolean true if $member can restore, and false if they can't. |
||
427 | */ |
||
428 | View Code Duplication | public function canRestore($member = null) { |
|
446 | |||
447 | /** |
||
448 | * Allows only selected {@link Member} objects to backup this {@link DNEnvironment} to a {@link DNDataArchive} |
||
449 | * file. |
||
450 | * |
||
451 | * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember(); |
||
452 | * @return boolean true if $member can backup, and false if they can't. |
||
453 | */ |
||
454 | View Code Duplication | public function canBackup($member = null) { |
|
477 | |||
478 | /** |
||
479 | * Allows only selected {@link Member} objects to upload {@link DNDataArchive} objects linked to this |
||
480 | * {@link DNEnvironment}. |
||
481 | * |
||
482 | * Note: This is not uploading them to the actual environment itself (e.g. uploading to the live site) - it is the |
||
483 | * process of uploading a *.sspak file into Deploynaut for later 'restoring' to an environment. See |
||
484 | * {@link self::canRestore()}. |
||
485 | * |
||
486 | * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember(); |
||
487 | * @return boolean true if $member can upload archives linked to this environment, false if they can't. |
||
488 | */ |
||
489 | View Code Duplication | public function canUploadArchive($member = null) { |
|
512 | |||
513 | /** |
||
514 | * Allows only selected {@link Member} objects to download {@link DNDataArchive} objects from this |
||
515 | * {@link DNEnvironment}. |
||
516 | * |
||
517 | * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember(); |
||
518 | * @return boolean true if $member can download archives from this environment, false if they can't. |
||
519 | */ |
||
520 | View Code Duplication | public function canDownloadArchive($member = null) { |
|
538 | |||
539 | /** |
||
540 | * Determine if the specified user can abort any pipelines |
||
541 | * |
||
542 | * @param Member|null $member |
||
543 | * @return boolean |
||
544 | */ |
||
545 | View Code Duplication | public function canAbort($member = null) { |
|
560 | |||
561 | /** |
||
562 | * Determine if the specified user can approve any pipelines |
||
563 | * |
||
564 | * @param Member|null $member |
||
565 | * @return boolean |
||
566 | */ |
||
567 | View Code Duplication | public function canApprove($member = null) { |
|
581 | |||
582 | /** |
||
583 | * Allows only selected {@link Member} objects to delete {@link DNDataArchive} objects from this |
||
584 | * {@link DNEnvironment}. |
||
585 | * |
||
586 | * @param Member|null $member The {@link Member} object to test against. If null, uses Member::currentMember(); |
||
587 | * @return boolean true if $member can delete archives from this environment, false if they can't. |
||
588 | */ |
||
589 | View Code Duplication | public function canDeleteArchive($member = null) { |
|
607 | /** |
||
608 | * Get a string of groups/people that are allowed to deploy to this environment. |
||
609 | * Used in DNRoot_project.ss to list {@link Member}s who have permission to perform this action. |
||
610 | * |
||
611 | * @return string |
||
612 | */ |
||
613 | public function getDeployersList() { |
||
622 | |||
623 | /** |
||
624 | * Get a string of groups/people that are allowed to restore {@link DNDataArchive} objects into this environment. |
||
625 | * |
||
626 | * @return string |
||
627 | */ |
||
628 | public function getCanRestoreMembersList() { |
||
637 | |||
638 | /** |
||
639 | * Get a string of groups/people that are allowed to backup {@link DNDataArchive} objects from this environment. |
||
640 | * |
||
641 | * @return string |
||
642 | */ |
||
643 | public function getCanBackupMembersList() { |
||
652 | |||
653 | /** |
||
654 | * Get a string of groups/people that are allowed to upload {@link DNDataArchive} |
||
655 | * objects linked to this environment. |
||
656 | * |
||
657 | * @return string |
||
658 | */ |
||
659 | public function getArchiveUploadersList() { |
||
668 | |||
669 | /** |
||
670 | * Get a string of groups/people that are allowed to download {@link DNDataArchive} objects from this environment. |
||
671 | * |
||
672 | * @return string |
||
673 | */ |
||
674 | public function getArchiveDownloadersList() { |
||
683 | |||
684 | /** |
||
685 | * Get a string of groups/people that are allowed to delete {@link DNDataArchive} objects from this environment. |
||
686 | * |
||
687 | * @return string |
||
688 | */ |
||
689 | public function getArchiveDeletersList() { |
||
698 | |||
699 | /** |
||
700 | * Get a string of groups/people that are allowed to approve pipelines |
||
701 | * |
||
702 | * @return string |
||
703 | */ |
||
704 | public function getPipelineApproversList() { |
||
713 | |||
714 | /** |
||
715 | * Get a string of groups/people that are allowed to cancel pipelines |
||
716 | * |
||
717 | * @return string |
||
718 | */ |
||
719 | public function getPipelineCancellersList() { |
||
728 | |||
729 | /** |
||
730 | * @return DNData |
||
731 | */ |
||
732 | public function DNData() { |
||
735 | |||
736 | /** |
||
737 | * Get the current deployed build for this environment |
||
738 | * |
||
739 | * Dear people of the future: If you are looking to optimize this, simply create a CurrentBuildSHA(), which can be |
||
740 | * a lot faster. I presume you came here because of the Project display template, which only needs a SHA. |
||
741 | * |
||
742 | * @return false|DNDeployment |
||
743 | */ |
||
744 | public function CurrentBuild() { |
||
776 | |||
777 | /** |
||
778 | * A history of all builds deployed to this environment |
||
779 | * |
||
780 | * @return ArrayList |
||
781 | */ |
||
782 | public function DeployHistory() { |
||
787 | |||
788 | /** |
||
789 | * @param string $sha |
||
790 | * @return array |
||
791 | */ |
||
792 | protected function getCommitData($sha) { |
||
816 | |||
817 | /** |
||
818 | * @return string |
||
819 | */ |
||
820 | public function Link() { |
||
823 | |||
824 | /** |
||
825 | * Is this environment currently at the root level of the controller that handles it? |
||
826 | * @return bool |
||
827 | */ |
||
828 | public function isCurrent() { |
||
831 | |||
832 | /** |
||
833 | * Is this environment currently in a controller that is handling it or performing a sub-task? |
||
834 | * @return bool |
||
835 | */ |
||
836 | public function isSection() { |
||
841 | |||
842 | |||
843 | /** |
||
844 | * Build a set of multi-select fields for assigning permissions to a pair of group and member many_many relations |
||
845 | * |
||
846 | * @param string $groupField Group field name |
||
847 | * @param string $memberField Member field name |
||
848 | * @param array $groups List of groups |
||
849 | * @param array $members List of members |
||
850 | * @return FieldGroup |
||
851 | */ |
||
852 | protected function buildPermissionField($groupField, $memberField, $groups, $members) { |
||
867 | |||
868 | /** |
||
869 | * @return FieldList |
||
870 | */ |
||
871 | public function getCMSFields() { |
||
1046 | |||
1047 | /** |
||
1048 | * @param FieldList $fields |
||
1049 | */ |
||
1050 | protected function setDeployConfigurationFields(&$fields) { |
||
1070 | |||
1071 | /** |
||
1072 | * @param FieldList $fields |
||
1073 | */ |
||
1074 | protected function setPipelineConfigurationFields($fields) { |
||
1099 | |||
1100 | /** |
||
1101 | */ |
||
1102 | public function onBeforeWrite() { |
||
1111 | |||
1112 | public function onAfterWrite() { |
||
1127 | |||
1128 | |||
1129 | /** |
||
1130 | * Ensure that environment paths are setup on the local filesystem |
||
1131 | */ |
||
1132 | protected function checkEnvironmentPath() { |
||
1139 | |||
1140 | /** |
||
1141 | * Write the deployment config file to filesystem |
||
1142 | */ |
||
1143 | protected function writeConfigFile() { |
||
1159 | |||
1160 | /** |
||
1161 | * Write the pipeline config file to filesystem |
||
1162 | */ |
||
1163 | protected function writePipelineFile() { |
||
1176 | |||
1177 | /** |
||
1178 | * Delete any related config files |
||
1179 | */ |
||
1180 | public function onAfterDelete() { |
||
1192 | |||
1193 | /** |
||
1194 | * @return string |
||
1195 | */ |
||
1196 | protected function getEnvironmentConfig() { |
||
1202 | |||
1203 | /** |
||
1204 | * @return boolean |
||
1205 | */ |
||
1206 | protected function envFileExists() { |
||
1212 | |||
1213 | /** |
||
1214 | * Returns the path to the ruby config file |
||
1215 | * |
||
1216 | * @return string |
||
1217 | */ |
||
1218 | public function getConfigFilename() { |
||
1227 | |||
1228 | /** |
||
1229 | * Returns the path to the {@link Pipeline} configuration for this environment. |
||
1230 | * Uses the same path and filename as the capistrano config, but with .yml extension. |
||
1231 | * |
||
1232 | * @return string |
||
1233 | */ |
||
1234 | public function getPipelineFilename() { |
||
1244 | |||
1245 | /** |
||
1246 | * Does this environment have a pipeline config file |
||
1247 | * |
||
1248 | * @return boolean |
||
1249 | */ |
||
1250 | protected function pipelineFileExists() { |
||
1257 | |||
1258 | /** |
||
1259 | * Helper function to convert a multi-dimensional array (associative or indexed) to an {@link ArrayList} or |
||
1260 | * {@link ArrayData} object structure, so that values can be used in templates. |
||
1261 | * |
||
1262 | * @param array $array The (single- or multi-dimensional) array to convert |
||
1263 | * @return object Either an {@link ArrayList} or {@link ArrayData} object, or the original item ($array) if $array |
||
1264 | * isn't an array. |
||
1265 | */ |
||
1266 | public static function array_to_viewabledata($array) { |
||
1291 | |||
1292 | |||
1293 | |||
1294 | /** |
||
1295 | * Helper function to retrieve filtered commits from an environment |
||
1296 | * this environment depends on |
||
1297 | * |
||
1298 | * @return DataList |
||
1299 | */ |
||
1300 | public function getDependentFilteredCommits() { |
||
1325 | |||
1326 | /** |
||
1327 | * Enable the maintenance page |
||
1328 | * |
||
1329 | * @param DeploynautLogFile $log |
||
1330 | */ |
||
1331 | public function enableMaintenace($log) { |
||
1335 | |||
1336 | /** |
||
1337 | * Disable maintenance page |
||
1338 | * |
||
1339 | * @param DeploynautLogFile $log |
||
1340 | */ |
||
1341 | public function disableMaintenance($log) { |
||
1345 | |||
1346 | protected function validate() { |
||
1356 | |||
1357 | /** |
||
1358 | * Fetchs all deployments in progress. Limits to 1 hour to prevent deployments |
||
1359 | * if an old deployment is stuck. |
||
1360 | * |
||
1361 | * @return DataList |
||
1362 | */ |
||
1363 | public function runningDeployments() { |
||
1371 | |||
1372 | } |
||
1373 | |||
1374 |
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.