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:
1 | <?php |
||
25 | class RollbackStep extends LongRunningPipelineStep { |
||
26 | |||
27 | /** |
||
28 | * @var array |
||
29 | */ |
||
30 | private static $db = array( |
||
31 | 'Doing' => "Enum('Deployment,Snapshot,Queued', 'Queued')" |
||
32 | ); |
||
33 | |||
34 | /** |
||
35 | * @var array |
||
36 | */ |
||
37 | private static $has_one = array( |
||
38 | 'RollbackDeployment' => 'DNDeployment', |
||
39 | 'RollbackDatabase' => 'DNDataTransfer' |
||
40 | ); |
||
41 | |||
42 | /** |
||
43 | * @return string |
||
44 | */ |
||
45 | public function getTitle() { |
||
49 | |||
50 | /** |
||
51 | * @return bool|null |
||
52 | */ |
||
53 | View Code Duplication | public function start() { |
|
|
|||
54 | parent::start(); |
||
55 | |||
56 | switch($this->Status) { |
||
57 | case 'Started': |
||
58 | // If we are doing a subtask, check which one to continue |
||
59 | switch($this->Doing) { |
||
60 | case 'Deployment': |
||
61 | return $this->continueRevertDeploy(); |
||
62 | case 'Snapshot': |
||
63 | return $this->continueRevertDatabase(); |
||
64 | default: |
||
65 | $this->log("Unable to process {$this->Title} with subtask of {$this->Doing}"); |
||
66 | $this->markFailed(); |
||
67 | return false; |
||
68 | } |
||
69 | case 'Queued': |
||
70 | // Begin rollback by initiating deployment |
||
71 | return $this->startRevertDeploy(); |
||
72 | default: |
||
73 | $this->log("Unable to process {$this->Title} with status of {$this->Status}"); |
||
74 | $this->markFailed(); |
||
75 | return false; |
||
76 | } |
||
77 | } |
||
78 | |||
79 | /** |
||
80 | * Begin a new deployment |
||
81 | * |
||
82 | * @return boolean |
||
83 | */ |
||
84 | protected function startRevertDeploy() { |
||
85 | $this->Status = 'Started'; |
||
86 | $this->Doing = 'Deployment'; |
||
87 | $this->log("{$this->Title} starting revert deployment"); |
||
88 | |||
89 | // Skip deployment for dry run |
||
90 | if($this->Pipeline()->DryRun) { |
||
91 | $this->log("[Skipped] Create DNDeployment"); |
||
92 | $this->write(); |
||
93 | return true; |
||
94 | } |
||
95 | |||
96 | // Get old deployment from pipeline |
||
97 | $pipeline = $this->Pipeline(); |
||
98 | $previous = $pipeline->PreviousDeployment(); |
||
99 | if(empty($previous) || empty($previous->SHA)) { |
||
100 | $this->log("No available SHA for {$this->Title}"); |
||
101 | $this->markFailed(); |
||
102 | return false; |
||
103 | } |
||
104 | |||
105 | // Initialise deployment |
||
106 | $strategy = new DeploymentStrategy($pipeline->Environment(), array( |
||
107 | 'sha'=>$pipeline->SHA, |
||
108 | // Leave the maintenance page up if we are restoring the DB |
||
109 | 'leaveMaintenancePage' => $this->doRestoreDB() |
||
110 | )); |
||
111 | $deployment = $strategy->createDeployment(); |
||
112 | |||
113 | $deployment->DeployerID = $pipeline->AuthorID; |
||
114 | $deployment->write(); |
||
115 | $deployment->start(); |
||
116 | $this->RollbackDeploymentID = $deployment->ID; |
||
117 | $this->write(); |
||
118 | |||
119 | return true; |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Create a snapshot of the db and store the ID on the Pipline |
||
124 | * |
||
125 | * @return bool True if success |
||
126 | */ |
||
127 | protected function startRevertDatabase() { |
||
128 | // Mark self as creating a snapshot |
||
129 | $this->Status = 'Started'; |
||
130 | $this->Doing = 'Snapshot'; |
||
131 | $this->log("{$this->Title} reverting database from snapshot"); |
||
132 | |||
133 | // Skip deployment for dry run |
||
134 | if($this->Pipeline()->DryRun) { |
||
135 | $this->write(); |
||
136 | $this->log("[Skipped] Create DNDataTransfer restore"); |
||
137 | return true; |
||
138 | } |
||
139 | |||
140 | // Get snapshot |
||
141 | $pipeline = $this->Pipeline(); |
||
142 | $backup = $pipeline->PreviousSnapshot(); |
||
143 | if(empty($backup) || !$backup->exists()) { |
||
144 | $this->log("No database to revert for {$this->Title}"); |
||
145 | $this->markFailed(); |
||
146 | return false; |
||
147 | } |
||
148 | |||
149 | // Create restore job |
||
150 | $job = DNDataTransfer::create(); |
||
151 | $job->EnvironmentID = $pipeline->EnvironmentID; |
||
152 | $job->Direction = 'push'; |
||
153 | $job->Mode = 'db'; |
||
154 | $job->DataArchiveID = $backup->DataArchiveID; |
||
155 | $job->AuthorID = $pipeline->AuthorID; |
||
156 | $job->EnvironmentID = $pipeline->EnvironmentID; |
||
157 | $job->write(); |
||
158 | $job->start(); |
||
159 | |||
160 | // Save rollback |
||
161 | $this->RollbackDatabaseID = $job->ID; |
||
162 | $this->write(); |
||
163 | return true; |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Check status of current snapshot |
||
168 | */ |
||
169 | View Code Duplication | protected function continueRevertDatabase() { |
|
170 | $this->log("Checking status of {$this->Title}..."); |
||
171 | |||
172 | // Skip snapshot for dry run |
||
173 | if($this->Pipeline()->DryRun) { |
||
174 | $this->log("[Skipped] Checking progress of snapshot restore"); |
||
175 | return $this->finish(); |
||
176 | } |
||
177 | |||
178 | // Get related snapshot |
||
179 | $transfer = $this->RollbackDatabase(); |
||
180 | if(empty($transfer) || !$transfer->exists()) { |
||
181 | $this->log("Missing database transfer for in-progress {$this->Title}"); |
||
182 | $this->markFailed(); |
||
183 | return false; |
||
184 | } |
||
185 | |||
186 | // Check finished state |
||
187 | $status = $transfer->ResqueStatus(); |
||
188 | if($this->checkResqueStatus($status)) { |
||
189 | // Re-enable the site by disabling the maintenance, since the DB restored successfully |
||
190 | $this->Pipeline()->Environment()->disableMaintenance($this->Pipeline()->getLogger()); |
||
191 | |||
192 | // After revert is complete we are done |
||
193 | return $this->finish(); |
||
194 | } |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Check status of deployment and finish task if complete, or fail if timedout |
||
199 | * |
||
200 | * @return boolean |
||
201 | */ |
||
202 | protected function continueRevertDeploy() { |
||
203 | $this->log("Checking status of {$this->Title}..."); |
||
204 | |||
205 | // Skip deployment for dry run |
||
206 | if($this->Pipeline()->DryRun) { |
||
207 | $this->log("[Skipped] Checking progress of deployment"); |
||
208 | if($this->getConfigSetting('RestoreDB')) { |
||
209 | return $this->startRevertDatabase(); |
||
210 | } else { |
||
211 | $this->finish(); |
||
212 | return true; |
||
213 | } |
||
214 | } |
||
215 | |||
216 | // Get related deployment |
||
217 | $deployment = $this->RollbackDeployment(); |
||
218 | if(empty($deployment) || !$deployment->exists()) { |
||
219 | $this->log("Missing deployment for in-progress {$this->Title}"); |
||
220 | $this->markFailed(); |
||
221 | return false; |
||
222 | } |
||
223 | |||
224 | // Check finished state |
||
225 | $status = $deployment->ResqueStatus(); |
||
226 | if($this->checkResqueStatus($status)) { |
||
227 | // Since deployment is finished, check if we should also do a db restoration |
||
228 | if($this->doRestoreDB()) { |
||
229 | return $this->startRevertDatabase(); |
||
230 | } else { |
||
231 | $this->finish(); |
||
232 | } |
||
233 | } |
||
234 | return !$this->isFailed(); |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Check if we are intending to restore the DB after this deployment |
||
239 | * |
||
240 | * @return boolean |
||
241 | */ |
||
242 | protected function doRestoreDB() { |
||
245 | |||
246 | /** |
||
247 | * Check the status of a resque sub-task |
||
248 | * |
||
249 | * @param string $status Resque task status |
||
250 | * @return boolean True if the task is finished successfully |
||
251 | */ |
||
252 | View Code Duplication | protected function checkResqueStatus($status) { |
|
278 | } |
||
279 |
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.