Completed
Pull Request — master (#488)
by Helpful
1295:51 queued 1292:33
created

DeployForm::buildCommitSelector()   F

Complexity

Conditions 11
Paths 384

Size

Total Lines 88
Code Lines 60

Duplication

Lines 20
Ratio 22.73 %
Metric Value
dl 20
loc 88
rs 3.8181
cc 11
eloc 60
nc 384
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Base class for a pipeline initiation validator
5
 *
6
 * @package deploynaut
7
 * @subpackage control
8
 */
9
abstract class DeployForm_ValidatorBase extends Validator {
10
11
	/**
12
	 * @param string $fieldName
13
	 * @param string $message
14
	 * @param string $messageType
15
	 */
16
	public function validationError($fieldName, $message, $messageType = '') {
17
		// Just make any error use the form message
18
		$this->form->sessionMessage($message, $messageType);
19
		parent::validationError($fieldName, $message, $messageType);
20
	}
21
22
	/**
23
	 * Validate a commit sha
24
	 *
25
	 * @param string $sha
26
	 * @param string $field
27
	 * @return boolean
28
	 */
29
	protected function validateCommit($sha, $field) {
30
		// Check selected commit
31
		if(empty($sha)) {
32
			$this->validationError(
33
				$field,
34
				"No valid release selected",
35
				"required"
36
			);
37
			return false;
38
		}
39
40
		// Check validity of commit
41
		if(!preg_match('/^[a-f0-9]{40}$/', $sha)) {
42
			$this->validationError(
43
				$field,
44
				"Invalid release SHA: " . Convert::raw2xml($sha),
45
				"error"
46
			);
47
			return false;
48
		}
49
50
		return true;
51
	}
52
}
53
54
/**
55
 * Validates a multi-source commit selector
56
 *
57
 * @package deploynaut
58
 * @subpackage control
59
 */
60
class DeployForm_CommitValidator extends DeployForm_ValidatorBase {
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
61
62
	public function php($data) {
63
		// Check release method
64
		if(empty($data['SelectRelease'])
65
			|| !in_array($data['SelectRelease'], array('Tag', 'Branch', 'Redeploy', 'SHA', 'FilteredCommits'))
66
		) {
67
			$method = empty($data['SelectRelease']) ? '(blank)' : $data['SelectRelease'];
68
			$this->validationError(
69
				'SelectRelease',
70
				"Bad release selection method: $method",
71
				"error"
72
			);
73
			return false;
74
		}
75
76
		// Check sha
77
		return $this->validateCommit(
78
			$this->form->getSelectedBuild($data),
79
			'SelectRelease'
80
		);
81
	}
82
83
}
84
85
/**
86
 * Validates a pipeline commit selector
87
 *
88
 * @package deploynaut
89
 * @subpackage control
90
 */
91
class DeployForm_PipelineValidator extends DeployForm_ValidatorBase {
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
92
93
	public function php($data) {
94
		return $this->validateCommit(
95
			$this->form->getSelectedBuild($data),
96
			'FilteredCommits'
97
		);
98
	}
99
}
100
101
/**
102
 * Form for generating deployments from a specified commit
103
 *
104
 * @package deploynaut
105
 * @subpackage control
106
 */
107
class DeployForm extends Form {
108
109
	/**
110
	 * @param DNRoot $controller
111
	 * @param string $name
112
	 * @param DNEnvironment $environment
113
	 * @param DNProject $project
114
	 */
115
	public function __construct($controller, $name, DNEnvironment $environment, DNProject $project) {
116
		if($environment->HasPipelineSupport()) {
117
			list($field, $validator, $actions) = $this->setupPipeline($environment, $project);
118
		} else {
119
			list($field, $validator, $actions) = $this->setupSimpleDeploy($project);
120
		}
121
		parent::__construct($controller, $name, new FieldList($field), $actions, $validator);
122
	}
123
124
	/**
125
	 * @param DNProject $project
126
	 *
127
	 * @return array
128
	 */
129
	protected function setupSimpleDeploy(DNProject $project) {
130
		// without a pipeline simply allow any commit to be selected
131
		$field = $this->buildCommitSelector($project);
132
		$validator = new DeployForm_CommitValidator();
133
		$actions = new FieldList(
134
			new FormAction('showDeploySummary', 'Plan deployment', 'Show deployment plan'),
0 ignored issues
show
Documentation introduced by
'Show deployment plan' is of type string, but the function expects a object<Form>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
135
			new FormAction('doDeploy', 'Do deploy', 'Do deploy')
0 ignored issues
show
Documentation introduced by
'Do deploy' is of type string, but the function expects a object<Form>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
136
		);
137
		return array($field, $validator, $actions);
138
	}
139
140
	/**
141
	 * @param DNEnvironment $environment
142
	 * @param DNProject $project
143
	 *
144
	 * @return array
145
	 * @throws Exception
146
	 */
147
	protected function setupPipeline(DNEnvironment $environment, DNProject $project) {
148
		// Determine if commits are filtered
149
		$canBypass = Permission::check(DNRoot::DEPLOYNAUT_BYPASS_PIPELINE);
150
		$canDryrun = $environment->DryRunEnabled && Permission::check(DNRoot::DEPLOYNAUT_DRYRUN_PIPELINE);
151
		$commits = $environment->getDependentFilteredCommits();
152
		if(empty($commits)) {
153
			// There are no filtered commits, so show all commits
154
			$field = $this->buildCommitSelector($project);
155
			$validator = new DeployForm_CommitValidator();
156
		} elseif($canBypass) {
157
			// Build hybrid selector that allows users to follow pipeline or use any commit
158
			$field = $this->buildCommitSelector($project, $commits);
159
			$validator = new DeployForm_CommitValidator();
160
		} else {
161
			// Restrict user to only select pipeline filtered commits
162
			$field = $this->buildPipelineField($commits);
163
			$validator = new DeployForm_PipelineValidator();
164
		}
165
166
		// Generate actions allowed for this user
167
		$actions = new FieldList(
168
			FormAction::create('startPipeline', "Begin the release process on " . $environment->Name)
169
				->addExtraClass('btn btn-primary')
170
				->setAttribute('onclick', "return confirm('This will begin a release pipeline. Continue?');")
171
		);
172
		if($canDryrun) {
173
			$actions->push(
174
				FormAction::create('doDryRun', "Dry-run release process")
175
					->addExtraClass('btn btn-info')
176
					->setAttribute(
177
						'onclick',
178
						"return confirm('This will begin a release pipeline, but with the following exclusions:\\n" .
179
						" - No messages will be sent\\n" .
180
						" - No capistrano actions will be invoked\\n" .
181
						" - No deployments or snapshots will be created.');"
182
					)
183
			);
184
		}
185
		if($canBypass) {
186
			$actions->push(
187
				FormAction::create('showDeploySummary', "Direct deployment (bypass pipeline)")
188
					->addExtraClass('btn btn-warning')
189
					->setAttribute(
190
						'onclick',
191
						"return confirm('This will start a direct deployment, bypassing the pipeline " .
192
						"process in place.\\n\\nAre you sure this is necessary?');"
193
					)
194
			);
195
			return array($field, $validator, $actions);
196
		}
197
		return array($field, $validator, $actions);
198
	}
199
200
	/**
201
	 * Construct fields to select any commit
202
	 *
203
	 * @param DNProject $project
204
	 * @param DataList|null $pipelineCommits Optional list of pipeline-filtered commits to include
205
	 * @return FormField
206
	 */
207
	protected function buildCommitSelector($project, $pipelineCommits = null) {
208
		// Branches
209
		$branches = array();
210 View Code Duplication
		foreach($project->DNBranchList() as $branch) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
211
			$sha = $branch->SHA();
212
			$name = $branch->Name();
213
			$branchValue = sprintf("%s (%s, %s old)",
214
				$name,
215
				substr($sha, 0, 8),
216
				$branch->LastUpdated()->TimeDiff()
217
			);
218
			$branches[$sha . '-' . $name] = $branchValue;
219
		}
220
221
		// Tags
222
		$tags = array();
223 View Code Duplication
		foreach($project->DNTagList()->setLimit(null) as $tag) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
224
			$sha = $tag->SHA();
225
			$name = $tag->Name();
226
			$tagValue = sprintf("%s (%s, %s old)",
227
				$name,
228
				substr($sha, 0, 8),
229
				$branch->LastUpdated()->TimeDiff()
0 ignored issues
show
Bug introduced by
The variable $branch seems to be defined by a foreach iteration on line 210. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
230
			);
231
			$tags[$sha . '-' . $tag] = $tagValue;
232
		}
233
		$tags = array_reverse($tags);
234
235
		// Past deployments
236
		$redeploy = array();
237
		foreach($project->DNEnvironmentList() as $dnEnvironment) {
238
			$envName = $dnEnvironment->Name;
239
			foreach($dnEnvironment->DeployHistory() as $deploy) {
240
				$sha = $deploy->SHA;
241
				if(!isset($redeploy[$envName])) {
242
					$redeploy[$envName] = array();
243
				}
244
				if(!isset($redeploy[$envName][$sha])) {
245
					$pastValue = sprintf("%s (deployed %s)",
246
						substr($sha, 0, 8),
247
						$deploy->obj('LastEdited')->Ago()
248
					);
249
					$redeploy[$envName][$sha] = $pastValue;
250
				}
251
			}
252
		}
253
254
		// Merge fields
255
		$releaseMethods = array();
256
		if($pipelineCommits) {
257
			$releaseMethods[] = new SelectionGroup_Item(
258
				'FilteredCommits',
259
				$this->buildPipelineField($pipelineCommits),
260
				'Deploy a commit prepared for this pipeline'
261
			);
262
		}
263
		if(!empty($branches)) {
264
			$releaseMethods[] = new SelectionGroup_Item(
265
				'Branch',
266
				new DropdownField('Branch', 'Select a branch', $branches),
267
				'Deploy the latest version of a branch'
268
			);
269
		}
270
		if($tags) {
271
			$releaseMethods[] = new SelectionGroup_Item(
272
				'Tag',
273
				new DropdownField('Tag', 'Select a tag', $tags),
274
				'Deploy a tagged release'
275
			);
276
		}
277
		if($redeploy) {
278
			$releaseMethods[] = new SelectionGroup_Item(
279
				'Redeploy',
280
				new GroupedDropdownField('Redeploy', 'Redeploy', $redeploy),
281
				'Redeploy a release that was previously deployed (to any environment)'
282
			);
283
		}
284
285
		$releaseMethods[] = new SelectionGroup_Item(
286
			'SHA',
287
			new Textfield('SHA', 'Please specify the full SHA'),
288
			'Deploy a specific SHA'
289
		);
290
291
		$field = new TabbedSelectionGroup('SelectRelease', $releaseMethods);
292
		$field->setValue(reset($releaseMethods)->getValue());
293
		return $field;
294
	}
295
296
	/**
297
	 * Generate fields necessary to select from a filtered commit list
298
	 *
299
	 * @param DataList $commits List of commits
300
	 * @return FormField
301
	 */
302
	protected function buildPipelineField($commits) {
303
		// Get filtered commits
304
		$filteredCommits = array();
305
		foreach($commits as $commit) {
306
			$title = sprintf(
307
				"%s (%s, %s old)",
308
				$commit->Message,
309
				substr($commit->SHA, 0, 8),
310
				$commit->dbObject('Created')->TimeDiff()
311
			);
312
			$filteredCommits[$commit->SHA] = $title;
313
		}
314
		if($filteredCommits) {
315
			return new DropdownField('FilteredCommits', '', $filteredCommits);
316
		} else {
317
			return DropdownField::create('FilteredCommits)', '')
318
				->setEmptyString('No deployments available')
319
				->performDisabledTransformation();
320
		}
321
	}
322
323
	/**
324
	 * Get the build selected from the given data
325
	 *
326
	 * @param array $data
327
	 * @return string SHA of selected build
328
	 */
329
	public function getSelectedBuild($data) {
330
		if(isset($data['SelectRelease']) && !empty($data[$data['SelectRelease']])) {
331
			// Filter out the tag/branch name if required
332
			$array = explode('-', $data[$data['SelectRelease']]);
333
			return reset($array);
334
		}
335
		if(isset($data['FilteredCommits']) && !empty($data['FilteredCommits'])) {
336
			return $data['FilteredCommits'];
337
		}
338
	}
339
}
340