Completed
Pull Request — master (#582)
by Mateusz
03:31 queued 31s
created

DNDeployment   C

Complexity

Total Complexity 45

Size/Duplication

Total Lines 327
Duplicated Lines 3.36 %

Coupling/Cohesion

Components 2
Dependencies 18

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 45
c 7
b 0
f 0
lcom 2
cbo 18
dl 11
loc 327
rs 5.7894

21 Methods

Rating   Name   Duplication   Size   Complexity  
A getTitle() 0 3 1
A getFiniteState() 0 3 1
A setFiniteState() 0 4 1
A getStatus() 0 3 1
A getMachine() 0 3 1
A Link() 0 3 1
A LogLink() 0 3 1
A canView() 0 3 1
A logfile() 0 7 1
A log() 0 3 1
A LogContent() 0 3 1
A ResqueStatus() 0 3 1
A getRepository() 0 6 2
A getCommit() 0 12 3
A getCommitMessage() 0 11 3
A getTags() 0 15 4
B getFullDeployMessages() 0 27 5
A getTag() 0 7 2
A getDeploymentStrategy() 0 6 1
D getChanges() 0 26 10
B enqueueDeployment() 11 39 3

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DNDeployment 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 DNDeployment, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Class representing a single deplyoment (passed or failed) at a time to a particular environment
5
 *
6
 * @property string $SHA
7
 * @property string $ResqueToken
8
 * @property string $State
9
 *
10
 * @method DNEnvironment Environment()
11
 * @property int EnvironmentID
12
 * @method Member Deployer()
13
 * @property int DeployerID
14
 */
15
class DNDeployment extends DataObject implements Finite\StatefulInterface, HasStateMachine {
16
17
	const STATE_NEW = 'New';
18
	const STATE_SUBMITTED = 'Submitted';
19
	const STATE_INVALID = 'Invalid';
20
	const STATE_QUEUED = 'Queued';
21
	const STATE_DEPLOYING = 'Deploying';
22
	const STATE_ABORTING = 'Aborting';
23
	const STATE_COMPLETED = 'Completed';
24
	const STATE_FAILED = 'Failed';
25
26
	const TR_SUBMIT = 'submit';
27
	const TR_INVALIDATE = 'invalidate';
28
	const TR_QUEUE = 'queue';
29
	const TR_DEPLOY = 'deploy';
30
	const TR_ABORT = 'abort';
31
	const TR_COMPLETE = 'complete';
32
	const TR_FAIL = 'fail';
33
34
	/**
35
	 * @var array
36
	 */
37
	private static $db = array(
38
		"SHA" => "GitSHA",
39
		"ResqueToken" => "Varchar(255)",
40
		// The branch that was used to deploy this. Can't really be inferred from Git history because
41
		// the commit could appear in lots of branches that are irrelevant to the user when it comes
42
		// to deployment history, and the branch may have been deleted.
43
		"Branch" => "Varchar(255)",
44
		"State" => "Enum('New, Submitted, Invalid, Queued, Deploying, Aborting, Completed, Failed', 'New')",
45
		// JSON serialised DeploymentStrategy.
46
		"Strategy" => "Text"
47
	);
48
49
	/**
50
	 * @var array
51
	 */
52
	private static $has_one = array(
53
		"Environment" => "DNEnvironment",
54
		"Deployer" => "Member",
55
	);
56
57
	private static $default_sort = '"LastEdited" DESC';
58
59
	private static $dependencies = [
60
		'stateMachineFactory' => '%$StateMachineFactory'
61
	];
62
63
	public function getTitle() {
64
		return "#{$this->ID}: {$this->SHA} (Status: {$this->Status})";
0 ignored issues
show
Documentation introduced by
The property Status does not exist on object<DNDeployment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
65
	}
66
67
	private static $summary_fields = array(
68
		'LastEdited' => 'Last Edited',
69
		'SHA' => 'SHA',
70
		'State' => 'State',
71
		'Deployer.Name' => 'Deployer'
72
	);
73
74
	public function getFiniteState() {
75
        return $this->State;
76
    }
77
78
    public function setFiniteState($state) {
79
		$this->State = $state;
80
		$this->write();
81
    }
82
83
	public function getStatus() {
84
		return $this->State;
85
	}
86
87
	public function getMachine() {
88
		return $this->stateMachineFactory->forDNDeployment($this);
0 ignored issues
show
Documentation introduced by
The property stateMachineFactory does not exist on object<DNDeployment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
89
	}
90
91
	public function Link() {
92
		return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
93
	}
94
95
	public function LogLink() {
96
		return $this->Link() . '/log';
97
	}
98
99
	public function canView($member = null) {
100
		return $this->Environment()->canView($member);
101
	}
102
103
	/**
104
	 * Return a path to the log file.
105
	 * @return string
106
	 */
107
	protected function logfile() {
108
		return sprintf(
109
			'%s.%s.log',
110
			$this->Environment()->getFullName('.'),
111
			$this->ID
112
		);
113
	}
114
115
	/**
116
	 * @return DeploynautLogFile
117
	 */
118
	public function log() {
119
		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
120
	}
121
122
	public function LogContent() {
123
		return $this->log()->content();
124
	}
125
126
	/**
127
	 * This remains here for backwards compatibility - we don't want to expose Resque status in here.
128
	 * Resque job (DeployJob) will change statuses as part of its execution.
129
	 *
130
	 * @return string
131
	 */
132
	public function ResqueStatus() {
133
		return $this->State;
134
	}
135
136
137
	/**
138
	 * Fetch the git repository
139
	 *
140
	 * @return \Gitonomy\Git\Repository|null
141
	 */
142
	public function getRepository() {
143
		if(!$this->SHA) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->SHA of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
144
			return null;
145
		}
146
		return $this->Environment()->Project()->getRepository();
147
	}
148
149
150
	/**
151
	 * Gets the commit from source. The result is cached upstream in Repository.
152
	 *
153
	 * @return \Gitonomy\Git\Commit|null
154
	 */
155
	public function getCommit() {
156
		$repo = $this->getRepository();
157
		if($repo) {
158
			try {
159
				return $repo->getCommit($this->SHA);
160
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
161
				return null;
162
			}
163
		}
164
165
		return null;
166
	}
167
168
169
	/**
170
	 * Gets the commit message.
171
	 *
172
	 * @return string|null
173
	 */
174
	public function getCommitMessage() {
175
		$commit = $this->getCommit();
176
		if($commit) {
177
			try {
178
				return Convert::raw2xml($commit->getMessage());
179
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
180
				return null;
181
			}
182
		}
183
		return null;
184
	}
185
186
	/**
187
	 * Return all tags for the deployed commit.
188
	 *
189
	 * @return ArrayList
190
	 */
191
	public function getTags() {
192
		$returnTags = array();
193
		$repo = $this->getRepository();
194
		if($repo) {
195
			$tags = $repo->getReferences()->resolveTags($this->SHA);
196
			if(!empty($tags)) {
197
				foreach($tags as $tag) {
198
					$field = Varchar::create('Tag', '255');
199
					$field->setValue($tag->getName());
200
					$returnTags[] = $field;
201
				}
202
			}
203
		}
204
		return new ArrayList($returnTags);
205
	}
206
207
	/**
208
	 * Collate the list of additional flags to affix to this deployment.
209
	 * Elements of the array will be rendered literally.
210
	 *
211
	 * @return ArrayList
212
	 */
213
	public function getFullDeployMessages() {
214
		$strategy = $this->getDeploymentStrategy();
215
		if ($strategy->getActionCode()!=='full') return null;
216
217
		$changes = $strategy->getChangesModificationNeeded();
218
		$messages = [];
219
		foreach ($changes as $change => $details) {
220
			if ($change==='Code version') continue;
221
222
			$messages[] = [
223
				'Flag' => sprintf(
224
					'<span class="label label-default full-deploy-info-item">%s</span>',
225
					$change[0]
226
				),
227
				'Text' => sprintf('%s changed', $change)
228
			];
229
		}
230
231
		if (empty($messages)) {
232
			$messages[] = [
233
				'Flag' => '',
234
				'Text' => '<i>Environment changes have been made.</i>'
235
			];
236
		}
237
238
		return new ArrayList($messages);
239
	}
240
241
	/**
242
	 * Fetches the latest tag for the deployed commit
243
	 *
244
	 * @return \Varchar|null
245
	 */
246
	public function getTag() {
247
		$tags = $this->getTags();
248
		if($tags->count() > 0) {
249
			return $tags->last();
250
		}
251
		return null;
252
	}
253
254
	/**
255
	 * @return DeploymentStrategy
256
	 */
257
	public function getDeploymentStrategy() {
258
		$environment = $this->Environment();
259
		$strategy = new DeploymentStrategy($environment);
260
		$strategy->fromJSON($this->Strategy);
0 ignored issues
show
Documentation introduced by
The property Strategy does not exist on object<DNDeployment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
261
		return $strategy;
262
	}
263
264
	/**
265
	 * Return a list of things that are going to be deployed, such
266
	 * as the code version, and any infrastrucutral changes.
267
	 *
268
	 * @return ArrayList
269
	 */
270
	public function getChanges() {
271
		$list = new ArrayList();
272
		$strategy = $this->getDeploymentStrategy();
273
		foreach($strategy->getChanges() as $name => $change) {
274
			$changed = (isset($change['from']) && isset($change['to'])) ? $change['from'] != $change['to'] : null;
275
			$description = isset($change['description']) ? $change['description'] : '';
276
			$compareUrl = null;
277
278
			// if there is a compare URL, and a description or a change (something actually changed)
279
			// then show the URL. Otherwise don't show anything, as there is no comparison to be made.
280
			if ($changed || $description) {
281
				$compareUrl = isset($change['compareUrl']) ? $change['compareUrl'] : '';
282
			}
283
284
			$list->push(new ArrayData([
285
				'Name' => $name,
286
				'From' => isset($change['from']) ? $change['from'] : null,
287
				'To' => isset($change['to']) ? $change['to'] : null,
288
				'Description' => $description,
289
				'Changed' => $changed,
290
				'CompareUrl' => $compareUrl
291
			]));
292
		}
293
294
		return $list;
295
	}
296
297
	/**
298
	 * Start a resque job for this deployment
299
	 *
300
	 * @return string Resque token
301
	 */
302
	public function enqueueDeployment() {
303
		$environment = $this->Environment();
304
		$project = $environment->Project();
305
		$log = $this->log();
306
307
		$args = array(
308
			'environmentName' => $environment->Name,
309
			'repository' => $project->getLocalCVSPath(),
310
			'logfile' => $this->logfile(),
311
			'projectName' => $project->Name,
312
			'env' => $project->getProcessEnv(),
313
			'deploymentID' => $this->ID,
314
			'sigFile' => DeployJob::sig_file_for_data_object($this)
315
		);
316
317
		$strategy = $this->getDeploymentStrategy();
318
		// Inject options.
319
		$args = array_merge($args, $strategy->getOptions());
320
		// Make sure we use the SHA as it was written into this DNDeployment.
321
		$args['sha'] = $this->SHA;
322
323
		if(!$this->DeployerID) {
324
			$this->DeployerID = Member::currentUserID();
325
		}
326
327 View Code Duplication
		if($this->DeployerID) {
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...
328
			$deployer = $this->Deployer();
329
			$message = sprintf(
330
				'Deploy to %s initiated by %s (%s), with IP address %s',
331
				$environment->getFullName(),
332
				$deployer->getName(),
333
				$deployer->Email,
334
				Controller::curr()->getRequest()->getIP()
335
			);
336
			$log->write($message);
337
		}
338
339
		return Resque::enqueue('deploy', 'DeployJob', $args, true);
340
	}
341
}
342