Completed
Pull Request — master (#579)
by Mateusz
03:28
created

DNDeployment::getChanges()   D

Complexity

Conditions 10
Paths 25

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 26
rs 4.8196
cc 10
eloc 17
nc 25
nop 0

How to fix   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
use Finite\State\StateInterface;
4
5
/**
6
 * Class representing a single deplyoment (passed or failed) at a time to a particular environment
7
 *
8
 * @property string $SHA
9
 * @property string $ResqueToken
10
 * @property string $Status
11
 *
12
 * @method DNEnvironment Environment()
13
 * @property int EnvironmentID
14
 * @method Member Deployer()
15
 * @property int DeployerID
16
 */
17
class DNDeployment extends DataObject implements Finite\StatefulInterface, HasStateMachine {
18
19
	/**
20
	 * @var array
21
	 */
22
	private static $db = array(
23
		"SHA" => "GitSHA",
24
		"ResqueToken" => "Varchar(255)",
25
		// The branch that was used to deploy this. Can't really be inferred from Git history because
26
		// the commit could appear in lots of branches that are irrelevant to the user when it comes
27
		// to deployment history, and the branch may have been deleted.
28
		"Branch" => "Varchar(255)",
29
		"State" => "Enum('New, Approved, Invalid, Queued, Deploying, Aborting, Completed, Failed', 'New')",
30
		// JSON serialised DeploymentStrategy.
31
		"Strategy" => "Text"
32
	);
33
34
	/**
35
	 * @var array
36
	 */
37
	private static $has_one = array(
38
		"Environment" => "DNEnvironment",
39
		"Deployer" => "Member",
40
	);
41
42
	private static $default_sort = '"LastEdited" DESC';
43
44
	public function getTitle() {
45
		return "#{$this->ID}: {$this->SHA} (Status: {$this->Status})";
46
	}
47
48
	private static $summary_fields = array(
49
		'LastEdited' => 'Last Edited',
50
		'SHA' => 'SHA',
51
		'Status' => 'Status',
52
		'Deployer.Name' => 'Deployer'
53
	);
54
55
	public function getFiniteState() {
56
        return $this->State;
0 ignored issues
show
Documentation introduced by
The property State 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...
57
    }
58
59
    public function setFiniteState($state) {
60
        $this->State = $state;
0 ignored issues
show
Documentation introduced by
The property State does not exist on object<DNDeployment>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
61
		$this->write();
62
    }
63
64
	public function getStatus() {
65
		return $this->State;
0 ignored issues
show
Documentation introduced by
The property State 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...
66
	}
67
68
	public function getMachine() {
69
		$loader = new Finite\Loader\ArrayLoader([
70
			'class'   => 'DNDeployment',
71
			'states'  => [
72
				'New' => ['type' => StateInterface::TYPE_INITIAL],
73
				'Approved' => ['type' => StateInterface::TYPE_NORMAL],
74
				'Invalid' => ['type' => StateInterface::TYPE_NORMAL],
75
				'Queued' => ['type' => StateInterface::TYPE_NORMAL],
76
				'Deploying' => ['type' => StateInterface::TYPE_NORMAL],
77
				'Aborting' => ['type' => StateInterface::TYPE_FINAL],
78
				'Completed' => ['type' => StateInterface::TYPE_FINAL],
79
				'Failed' => ['type' => StateInterface::TYPE_FINAL],
80
			],
81
			'transitions' => [
82
				'approve'  => ['from' => ['New'], 'to' => 'Approved'],
83
				'queue'  => ['from' => ['Approved'], 'to' => 'Queued'],
84
				'invalidate'  => ['from' => ['New','Approved'], 'to' => 'Invalid'],
85
				'deploy'  => ['from' => ['Invalid'], 'to' => 'New'],
86
				'deploy'  => ['from' => ['Queued'], 'to' => 'Deploying'],
87
				'abort'  => ['from' => ['Queued', 'Deploying','Aborting'], 'to' => 'Aborting'],
88
				'complete' => ['from' => ['Deploying'], 'to' => 'Completed'],
89
				'fail'  => [
90
					'from' => ['New','Approved','Queued','Invalid','Deploying','Aborting'],
91
					'to' => 'Failed'
92
				],
93
			],
94
			'callbacks' => [
95
				'after' => [
96
					['to' => ['Queued'], 'do' => [$this, 'onQueue']],
97
					['to' => ['Aborting'], 'do' => [$this, 'onAbort']],
98
				]
99
			]
100
		]);
101
		$stateMachine = new Finite\StateMachine\StateMachine($this);
102
		$loader->load($stateMachine);
103
		$stateMachine->initialize();
104
		return $stateMachine;
105
	}
106
107
108
	public function onQueue() {
109
		$log = $this->log();
110
		$token = $this->enqueueDeployment();
111
		$this->ResqueToken = $token;
112
		$this->write();
113
114
		$message = sprintf('Deploy queued as job %s (sigFile is %s)', $token, DeployJob::sig_file_for_data_object($this));
115
		$log->write($message);
116
	}
117
118
	public function onAbort() {
119
		// 2 is SIGINT - we can't use SIGINT constant in the mod_apache context.
120
		DeployJob::set_signal($this, 2);
121
	}
122
123
	public function Link() {
124
		return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
125
	}
126
127
	public function LogLink() {
128
		return $this->Link() . '/log';
129
	}
130
131
	public function canView($member = null) {
132
		return $this->Environment()->canView($member);
133
	}
134
135
	/**
136
	 * Return a path to the log file.
137
	 * @return string
138
	 */
139
	protected function logfile() {
140
		return sprintf(
141
			'%s.%s.log',
142
			$this->Environment()->getFullName('.'),
143
			$this->ID
144
		);
145
	}
146
147
	/**
148
	 * @return DeploynautLogFile
149
	 */
150
	public function log() {
151
		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
152
	}
153
154
	public function LogContent() {
155
		return $this->log()->content();
156
	}
157
158
	/**
159
	 * This remains here for backwards compatibility - we don't want to expose Resque status in here.
160
	 * Resque job (DeployJob) will change statuses as part of its execution.
161
	 *
162
	 * @return string
163
	 */
164
	public function ResqueStatus() {
165
		return $this->State;
0 ignored issues
show
Documentation introduced by
The property State 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...
166
	}
167
168
169
	/**
170
	 * Fetch the git repository
171
	 *
172
	 * @return \Gitonomy\Git\Repository|null
173
	 */
174
	public function getRepository() {
175
		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...
176
			return null;
177
		}
178
		return $this->Environment()->Project()->getRepository();
179
	}
180
181
182
	/**
183
	 * Gets the commit from source. The result is cached upstream in Repository.
184
	 *
185
	 * @return \Gitonomy\Git\Commit|null
186
	 */
187
	public function getCommit() {
188
		$repo = $this->getRepository();
189
		if($repo) {
190
			try {
191
				return $repo->getCommit($this->SHA);
192
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
193
				return null;
194
			}
195
		}
196
197
		return null;
198
	}
199
200
201
	/**
202
	 * Gets the commit message.
203
	 *
204
	 * @return string|null
205
	 */
206
	public function getCommitMessage() {
207
		$commit = $this->getCommit();
208
		if($commit) {
209
			try {
210
				return Convert::raw2xml($commit->getMessage());
211
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
212
				return null;
213
			}
214
		}
215
		return null;
216
	}
217
218
	/**
219
	 * Return all tags for the deployed commit.
220
	 *
221
	 * @return ArrayList
222
	 */
223
	public function getTags() {
224
		$returnTags = array();
225
		$repo = $this->getRepository();
226
		if($repo) {
227
			$tags = $repo->getReferences()->resolveTags($this->SHA);
228
			if(!empty($tags)) {
229
				foreach($tags as $tag) {
230
					$field = Varchar::create('Tag', '255');
231
					$field->setValue($tag->getName());
232
					$returnTags[] = $field;
233
				}
234
			}
235
		}
236
		return new ArrayList($returnTags);
237
	}
238
239
	/**
240
	 * Collate the list of additional flags to affix to this deployment.
241
	 * Elements of the array will be rendered literally.
242
	 *
243
	 * @return ArrayList
244
	 */
245
	public function getFullDeployMessages() {
246
		$strategy = $this->getDeploymentStrategy();
247
		if ($strategy->getActionCode()!=='full') return null;
248
249
		$changes = $strategy->getChangesModificationNeeded();
250
		$messages = [];
251
		foreach ($changes as $change => $details) {
252
			if ($change==='Code version') continue;
253
254
			$messages[] = [
255
				'Flag' => sprintf(
256
					'<span class="label label-default full-deploy-info-item">%s</span>',
257
					$change[0]
258
				),
259
				'Text' => sprintf('%s changed', $change)
260
			];
261
		}
262
263
		if (empty($messages)) {
264
			$messages[] = [
265
				'Flag' => '',
266
				'Text' => '<i>Environment changes have been made.</i>'
267
			];
268
		}
269
270
		return new ArrayList($messages);
271
	}
272
273
	/**
274
	 * Fetches the latest tag for the deployed commit
275
	 *
276
	 * @return \Varchar|null
277
	 */
278
	public function getTag() {
279
		$tags = $this->getTags();
280
		if($tags->count() > 0) {
281
			return $tags->last();
282
		}
283
		return null;
284
	}
285
286
	/**
287
	 * @return DeploymentStrategy
288
	 */
289
	public function getDeploymentStrategy() {
290
		$environment = $this->Environment();
291
		$strategy = new DeploymentStrategy($environment);
292
		$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...
293
		return $strategy;
294
	}
295
296
	/**
297
	 * Return a list of things that are going to be deployed, such
298
	 * as the code version, and any infrastrucutral changes.
299
	 *
300
	 * @return ArrayList
301
	 */
302
	public function getChanges() {
303
		$list = new ArrayList();
304
		$strategy = $this->getDeploymentStrategy();
305
		foreach($strategy->getChanges() as $name => $change) {
306
			$changed = (isset($change['from']) && isset($change['to'])) ? $change['from'] != $change['to'] : null;
307
			$description = isset($change['description']) ? $change['description'] : '';
308
			$compareUrl = null;
309
310
			// if there is a compare URL, and a description or a change (something actually changed)
311
			// then show the URL. Otherwise don't show anything, as there is no comparison to be made.
312
			if ($changed || $description) {
313
				$compareUrl = isset($change['compareUrl']) ? $change['compareUrl'] : '';
314
			}
315
316
			$list->push(new ArrayData([
317
				'Name' => $name,
318
				'From' => isset($change['from']) ? $change['from'] : null,
319
				'To' => isset($change['to']) ? $change['to'] : null,
320
				'Description' => $description,
321
				'Changed' => $changed,
322
				'CompareUrl' => $compareUrl
323
			]));
324
		}
325
326
		return $list;
327
	}
328
329
	/**
330
	 * Start a resque job for this deployment
331
	 *
332
	 * @return string Resque token
333
	 */
334
	protected function enqueueDeployment() {
335
		$environment = $this->Environment();
336
		$project = $environment->Project();
337
		$log = $this->log();
338
339
		$args = array(
340
			'environmentName' => $environment->Name,
341
			'repository' => $project->getLocalCVSPath(),
342
			'logfile' => $this->logfile(),
343
			'projectName' => $project->Name,
344
			'env' => $project->getProcessEnv(),
345
			'deploymentID' => $this->ID,
346
			'sigFile' => DeployJob::sig_file_for_data_object($this)
347
		);
348
349
		$strategy = $this->getDeploymentStrategy();
350
		// Inject options.
351
		$args = array_merge($args, $strategy->getOptions());
352
		// Make sure we use the SHA as it was written into this DNDeployment.
353
		$args['sha'] = $this->SHA;
354
355
		if(!$this->DeployerID) {
356
			$this->DeployerID = Member::currentUserID();
357
		}
358
359 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...
360
			$deployer = $this->Deployer();
361
			$message = sprintf(
362
				'Deploy to %s initiated by %s (%s), with IP address %s',
363
				$environment->getFullName(),
364
				$deployer->getName(),
365
				$deployer->Email,
366
				Controller::curr()->getRequest()->getIP()
367
			);
368
			$log->write($message);
369
		}
370
371
		return Resque::enqueue('deploy', 'DeployJob', $args, true);
372
	}
373
}
374