Completed
Pull Request — master (#613)
by Stig
03:36
created

DeployPlanDispatcher   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 325
Duplicated Lines 12.31 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 5
Bugs 0 Features 1
Metric Value
wmc 35
c 5
b 0
f 1
lcom 1
cbo 13
dl 40
loc 325
rs 9

14 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 15 3
A Link() 0 3 1
A index() 0 6 1
A getFetch() 0 13 2
A createFetch() 17 17 1
B canCompareCodeVersions() 0 19 8
A gitupdate() 0 11 3
B gitrefs() 0 35 3
A getModel() 0 9 1
B deploysummary() 0 27 2
A getGitBranches() 0 10 2
A getGitTags() 0 10 2
B getGitPrevDeploys() 23 27 5
A getAPIResponse() 0 15 1

How to fix   Duplicated Code   

Duplicated Code

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
2
3
4
class DeployPlanDispatcher extends Dispatcher {
5
6
	const ACTION_PLAN = 'plan';
7
8
	/**
9
	 * @var array
10
	 */
11
	private static $action_types = [
12
		self::ACTION_PLAN
13
	];
14
15
	/**
16
	 * @var array
17
	 */
18
	public static $allowed_actions = [
19
		'gitupdate',
20
		'gitrefs',
21
		'deploysummary'
22
	];
23
24
	/**
25
	 * @var \DNProject
26
	 */
27
	protected $project = null;
28
29
	/**
30
	 * @var \DNEnvironment
31
	 */
32
	protected $environment = null;
33
34
	public function init() {
35
		parent::init();
36
37
		$this->project = $this->getCurrentProject();
38
39
		if(!$this->project) {
40
			return $this->project404Response();
41
		}
42
43
		// Performs canView permission check by limiting visible projects
44
		$this->environment = $this->getCurrentEnvironment($this->project);
45
		if(!$this->environment) {
46
			return $this->environment404Response();
47
		}
48
	}
49
50
	/**
51
	 * @return string
52
	 */
53
	public function Link() {
54
		return \Controller::join_links($this->environment->Link(), self::ACTION_PLAN);
55
	}
56
57
	/**
58
	 *
59
	 * @param \SS_HTTPRequest $request
60
	 *
61
	 * @return \HTMLText|\SS_HTTPResponse
62
	 */
63
	public function index(\SS_HTTPRequest $request) {
64
		$this->setCurrentActionType(self::ACTION_PLAN);
65
		return $this->customise([
66
			'Environment' => $this->environment
67
		])->renderWith(['Plan', 'DNRoot']);
68
	}
69
70
	/**
71
	 * @param SS_HTTPRequest $request
72
	 * @return SS_HTTPResponse
73
	 */
74
	public function gitupdate(SS_HTTPRequest $request) {
75
		switch($request->httpMethod()) {
76
			case 'POST':
77
				$this->checkSecurityToken(sprintf('%sSecurityID', get_class($this)));
78
				return $this->createFetch();
79
			case 'GET':
80
				return $this->getFetch($this->getRequest()->param('ID'));
81
			default:
82
				return $this->getAPIResponse(['message' => 'Method not allowed, requires POST or GET/{id}'], 405);
83
		}
84
	}
85
86
	/**
87
	 * @param SS_HTTPRequest $request
88
	 *
89
	 * @return string
90
	 */
91
	public function gitrefs(\SS_HTTPRequest $request) {
92
93
		$refs = [];
94
		$order = 0;
95
		$refs[] = [
96
			'id' => ++$order,
97
			'label' => "Branch version",
98
			"description" => "Deploy the latest version of a branch",
99
			"list" => $this->getGitBranches($this->project)
100
		];
101
102
		$refs[] = [
103
			'id' => ++$order,
104
			'label' => "Tag version",
105
			"description" => "Deploy a tagged release",
106
			"list" => $this->getGitTags($this->project)
107
		];
108
109
		// @todo: the original was a tree that was keyed by environment, the
110
		// front-end dropdown needs to be changed to support that. brrrr.
111
		$prevDeploys = [];
112
		foreach($this->getGitPrevDeploys($this->project) as $env) {
113
			foreach($env as $deploy) {
114
				$prevDeploys[] = $deploy;
115
			}
116
		}
117
		$refs[] = [
118
			'id' => ++$order,
119
			'label' => "Redeploy a release that was previously deployed (to any environment",
120
			"description" => "Deploy a previous release",
121
			"list" => $prevDeploys
122
		];
123
124
		return $this->getAPIResponse(['refs' => $refs], 200);
125
	}
126
127
	/**
128
	 * @param string $name
129
	 *
130
	 * @return array
131
	 */
132
	public function getModel($name = '') {
133
		return [
134
			'api_endpoint' => Director::absoluteBaseURL().$this->Link(),
135
			'api_auth' => [
136
				'name' => $this->getSecurityToken()->getName(),
137
				'value' => $this->getSecurityToken()->getValue()
138
			]
139
		];
140
	}
141
142
	/**
143
	 * @param int $ID
144
	 * @return SS_HTTPResponse
145
	 */
146
	protected function getFetch($ID) {
147
		$ping = DNGitFetch::get()->byID($ID);
148
		if(!$ping) {
149
			return $this->getAPIResponse(['message' => 'Fetch not found'], 404);
150
		}
151
		$output = [
152
			'id' => $ID,
153
			'status' => $ping->ResqueStatus(),
154
			'message' => array_filter(explode(PHP_EOL, $ping->LogContent()))
155
		];
156
157
		return $this->getAPIResponse($output, 200);
158
	}
159
160
	/**
161
	 * @return SS_HTTPResponse
162
	 */
163 View Code Duplication
	protected function createFetch() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
164
		/** @var DNGitFetch $fetch */
165
		$fetch = DNGitFetch::create();
166
		$fetch->ProjectID = $this->project->ID;
167
		$fetch->write();
168
		$fetch->start();
169
170
		$location = Director::absoluteBaseURL() . $this->Link() . '/gitupdate/' . $fetch->ID;
171
		$output = array(
172
			'message' => 'Fetch queued as job ' . $fetch->ResqueToken,
173
			'href' => $location,
174
		);
175
176
		$response = $this->getAPIResponse($output, 201);
177
		$response->addHeader('Location', $location);
178
		return $response;
179
	}
180
181
	/**
182
	 * @param SS_HTTPRequest $request
183
	 *
184
	 * @return SS_HTTPResponse
185
	 */
186
	public function deploysummary(SS_HTTPRequest $request) {
187
		$this->checkSecurityToken(sprintf('%sSecurityID', get_class($this)));
188
189
		// @todo permission checks?
190
191
		$options = [
192
			'sha' => $request->requestVar('sha')
193
		];
194
195
		$strategy = $this->environment->Backend()->planDeploy($this->environment, $options);
196
		$data = $strategy->toArray();
197
198
		$interface = $this->project->getRepositoryInterface();
199
		if($this->canCompareCodeVersions($interface, $data['changes'])) {
0 ignored issues
show
Bug introduced by
It seems like $interface defined by $this->project->getRepositoryInterface() on line 198 can be null; however, DeployPlanDispatcher::canCompareCodeVersions() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
200
			$compareurl = sprintf(
201
				'%s/compare/%s...%s',
202
				$interface->URL,
203
				$data['changes']['Code version']['from'],
204
				$data['changes']['Code version']['to']
205
			);
206
			$data['changes']['Code version']['compareUrl'] = $compareurl;
207
		}
208
209
		$this->extend('updateDeploySummary', $data);
210
211
		return $this->getAPIResponse($data, 201);
212
	}
213
214
	/**
215
	 * @param $project
216
	 *
217
	 * @return array
218
	 */
219
	protected function getGitBranches($project) {
220
		$branches = [];
221
		foreach($project->DNBranchList() as $branch) {
222
			$branches[] = [
223
				'key' => $branch->SHA(),
224
				'value' => $branch->Name(),
225
			];
226
		}
227
		return $branches;
228
	}
229
230
	/**
231
	 * @param $project
232
	 *
233
	 * @return array
234
	 */
235
	protected function getGitTags($project) {
236
		$tags = [];
237
		foreach($project->DNTagList()->setLimit(null) as $tag) {
238
			$tags[] = [
239
				'key' => $tag->SHA(),
240
				'value' => $tag->Name(),
241
			];
242
		}
243
		return $tags;
244
	}
245
246
	/**
247
	 * @param $project
248
	 *
249
	 * @return array
250
	 */
251
	protected function getGitPrevDeploys($project) {
252
		$redeploy = [];
253 View Code Duplication
		foreach($project->DNEnvironmentList() as $dnEnvironment) {
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...
254
			$envName = $dnEnvironment->Name;
255
			$perEnvDeploys = [];
256
			foreach($dnEnvironment->DeployHistory() as $deploy) {
257
				$sha = $deploy->SHA;
258
259
				// Check if exists to make sure the newest deployment date is used.
260
				if(!isset($perEnvDeploys[$sha])) {
261
					$pastValue = sprintf(
262
						"%s (deployed %s)",
263
						substr($sha, 0, 8),
264
						$deploy->obj('LastEdited')->Ago()
265
					);
266
					$perEnvDeploys[$sha] = [
267
						'key' => $sha,
268
						'value' => $pastValue
269
					];
270
				}
271
			}
272
			if(!empty($perEnvDeploys)) {
273
				$redeploy[$envName] = array_values($perEnvDeploys);
274
			}
275
		}
276
		return $redeploy;
277
	}
278
279
	/**
280
	 * Return a simple response with a message
281
	 *
282
	 * @param array $output
283
	 * @param int $statusCode
284
	 * @return SS_HTTPResponse
285
	 */
286
	protected function getAPIResponse($output, $statusCode) {
287
		$secToken = $this->getSecurityToken();
288
		$secToken->reset();
289
		$output['api_auth'] = [
290
			'name' => $secToken->getName(),
291
			'value' => $secToken->getValue()
292
		];
293
		$output['status_code'] = $statusCode;
294
		$body = json_encode($output, JSON_PRETTY_PRINT);
295
		$response = $this->getResponse();
296
		$response->addHeader('Content-Type', 'application/json');
297
		$response->setBody($body);
298
		$response->setStatusCode($statusCode);
299
		return $response;
300
	}
301
302
	/**
303
	 * @param ArrayData $interface
304
	 * @param $changes
305
	 *
306
	 * @return bool
307
	 *
308
	 */
309
	protected function canCompareCodeVersions(\ArrayData $interface, $changes) {
310
		if(empty($changes['Code version'])) {
311
			return false;
312
		}
313
		$codeVersion = ['Code version'];
314
		if(empty($interface)) {
315
			return false;
316
		}
317
		if(empty($interface->URL)) {
318
			return false;
319
		}
320
		if(empty($codeVersion['from']) || empty($codeVersion['to'])) {
321
			return false;
322
		}
323
		if(strlen($codeVersion['from']) !== 40 || strlen($codeVersion['to']) !== 40) {
324
			return false;
325
		}
326
		return true;
327
	}
328
}
329