DNDataTransfer   B
last analyzed

Complexity

Total Complexity 27

Size/Duplication

Total Lines 274
Duplicated Lines 14.6 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 16
dl 40
loc 274
rs 8.4614
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A map_resque_status() 10 10 1
A setBackupBeforePush() 0 3 1
A getTitle() 0 3 1
A Link() 0 3 1
A LogLink() 0 3 1
A getDefaultSearchContext() 0 7 1
B getCMSFields() 0 24 1
B start() 13 36 3
A canView() 0 3 1
A logfile() 0 7 1
A log() 0 3 1
A LogContent() 0 3 1
A getDescription() 0 16 4
A getModeNice() 0 7 2
A IsBackupDataTransfer() 0 19 3
A ResqueStatus() 17 17 4

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 representing a single data transfer in a project,
5
 * which can include a database export, an archive of all assets, or both.
6
 *
7
 * It can be one of two directions:
8
 * - Backup: Package up data on an environment and store it in a local file
9
 * - Restore: Transfer data from a local file into an environment, extract assets and/or restore a database
10
 *
11
 * The choice of database and/or assets is represented in the "Mode".
12
 * There's always one file archive involved (stored as the has_one "ArchiveFile") on the local Deploynaut environment.
13
 *
14
 * Each transfer is executed by a Resque job, so the model also contains
15
 * a reference to a Resque token (which might still be in progress).
16
 *
17
 * The "Environment" points to the source or target involved.
18
 *
19
 * @property string $ResqueToken
20
 * @property string $Status
21
 * @property string $Direction
22
 * @property string $Mode
23
 * @property string $Origin
24
 *
25
 * @method DNEnvironment Environment()
26
 * @property int EnvironmentID
27
 * @method Member Author()
28
 * @property int AuthorID
29
 * @method DNDataArchive DataArchive()
30
 * @property int DataArchiveID
31
 * @method DNDataTransfer BackupDataTransfer()
32
 * @property int BackupDataTransferID
33
 */
34
class DNDataTransfer extends DataObject {
0 ignored issues
show
Coding Style introduced by
The property $has_one is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $singular_name is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $plural_name is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $summary_fields is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $searchable_fields is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
35
36
	private static $db = array(
37
		"ResqueToken" => "Varchar(255)",
38
		// Observe that this is not the same as Resque status, since ResqueStatus is not persistent.
39
		"Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
40
		"Direction" => "Enum('get, push', 'get')",
41
		"Mode" => "Enum('all, assets, db', '')",
42
		"Origin" => "Enum('EnvironmentTransfer,ManualUpload', 'EnvironmentTransfer')",
43
		"IncludeResampled" => "Boolean",
44
	);
45
46
	private static $has_one = array(
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
47
		"Environment" => "DNEnvironment",
48
		"Author" => "Member",
49
		"DataArchive" => "DNDataArchive",
50
		"BackupDataTransfer" => "DNDataTransfer" // denotes an automated backup done for a push of this data transfer
51
	);
52
53
	private static $singular_name = 'Data Transfer';
0 ignored issues
show
Unused Code introduced by
The property $singular_name is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
54
55
	private static $plural_name = 'Data Transfers';
56
57
	private static $summary_fields = array(
58
		'Created' => 'Created',
59
		'Author.Title' => 'Author',
60
		'Environment.Project.Name' => 'Project',
61
		'Environment.Name' => 'Environment',
62
		'Status' => 'Status',
63
		'Origin' => 'Origin',
64
		'IncludeResampled.Nice' => 'Included Resampled?',
65
	);
66
67
	private static $searchable_fields = array(
68
		'Environment.Project.Name' => array(
69
			'title' => 'Project',
70
		),
71
		'Environment.Name' => array(
72
			'title' => 'Environment',
73
		),
74
		'Status' => array(
75
			'title' => 'Status',
76
		),
77
		'Origin' => array(
78
			'title' => 'Origin',
79
		),
80
		'Mode' => array(
81
			'title' => 'Mode',
82
		),
83
		'Direction' => array(
84
			'title' => 'Direction',
85
		),
86
	);
87
88
	/**
89
	 * When running the transfer, should a backup be performed before pushing the data?
90
	 * @var bool
91
	 */
92
	protected $backupBeforePush = true;
93
94
	/**
95
	 * @param int $int
96
	 * @return string
97
	 */
98 View Code Duplication
	public static function map_resque_status($int) {
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...
99
		$remap = array(
100
			Resque_Job_Status::STATUS_WAITING => "Queued",
101
			Resque_Job_Status::STATUS_RUNNING => "Running",
102
			Resque_Job_Status::STATUS_FAILED => "Failed",
103
			Resque_Job_Status::STATUS_COMPLETE => "Complete",
104
			false => "Invalid",
105
		);
106
		return $remap[$int];
107
	}
108
109
	/**
110
	 * @param boolean $value
111
	 */
112
	public function setBackupBeforePush($value) {
113
		$this->backupBeforePush = $value;
114
	}
115
116
	public function getTitle() {
117
		return $this->dbObject('Created')->Nice() . " (Status: {$this->Status})";
118
	}
119
120
	public function Link() {
121
		return Controller::join_links($this->Environment()->Project()->Link(), 'transfer', $this->ID);
122
	}
123
124
	public function LogLink() {
125
		return Controller::join_links($this->Link(), 'log');
126
	}
127
128
	public function getDefaultSearchContext() {
129
		$context = parent::getDefaultSearchContext();
130
		$context->getFields()->dataFieldByName('Status')->setHasEmptyDefault(true);
131
		$context->getFields()->dataFieldByName('Origin')->setHasEmptyDefault(true);
132
133
		return $context;
134
	}
135
136
	public function getCMSFields() {
137
		$fields = parent::getCMSFields();
138
		$fields->removeByName('EnvironmentID');
139
		$fields->removeByName('ArchiveFile');
140
		$fields->addFieldsToTab(
141
			'Root.Main',
142
			array(
143
				new ReadonlyField('ProjectName', 'Project', $this->Environment()->Project()->Name),
144
				new ReadonlyField('EnvironmentName', 'Environment', $this->Environment()->Name),
145
				new ReadonlyField(
146
					'DataArchive',
147
					'Archive File',
148
					sprintf(
149
						'<a href="%s">%s</a>',
150
						$this->DataArchive()->ArchiveFile()->AbsoluteURL,
151
						$this->DataArchive()->ArchiveFile()->Filename
152
					)
153
				),
154
			)
155
		);
156
		$fields = $fields->makeReadonly();
157
158
		return $fields;
159
	}
160
161
	/**
162
	 * Queue a transfer job
163
	 */
164
	public function start() {
165
		$env = $this->Environment();
166
		$log = $this->log();
167
168
		$args = array(
169
			'dataTransferID' => $this->ID,
170
			'logfile' => $this->logfile(),
171
			'backupBeforePush' => $this->backupBeforePush,
172
			'includeResampled' => $this->IncludeResampled,
0 ignored issues
show
Documentation introduced by
The property IncludeResampled does not exist on object<DNDataTransfer>. 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...
173
		);
174
175
		if(!$this->AuthorID) {
176
			$this->AuthorID = Member::currentUserID();
177
		}
178
179 View Code Duplication
		if($this->AuthorID) {
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...
180
			$author = $this->Author();
181
			$message = sprintf(
182
				'Data transfer on %s (%s, %s) initiated by %s (%s), with IP address %s',
183
				$env->getFullName(),
184
				$this->Direction,
185
				$this->Mode,
186
				$author->getName(),
187
				$author->Email,
188
				Controller::curr()->getRequest()->getIP()
189
			);
190
			$log->write($message);
191
		}
192
193
		$token = Resque::enqueue('snapshot', 'DataTransferJob', $args, true);
194
		$this->ResqueToken = $token;
195
		$this->write();
196
197
		$message = sprintf('Data transfer queued as job %s', $token);
198
		$log->write($message);
199
	}
200
201
	/**
202
	 * @param Member|null $member
203
	 * @return bool
204
	 */
205
	public function canView($member = null) {
206
		return $this->Environment()->canView($member);
207
	}
208
209
	/**
210
	 * Return a path to the log file.
211
	 * @return string
212
	 */
213
	protected function logfile() {
214
		return sprintf(
215
			'%s.datatransfer.%s.log',
216
			$this->Environment()->getFullName('.'),
217
			$this->ID
218
		);
219
	}
220
221
	/**
222
	 * @return \DeploynautLogFile
223
	 */
224
	public function log() {
225
		return new DeploynautLogFile($this->logfile());
226
	}
227
228
	/**
229
	 * @return string
230
	 */
231
	public function LogContent() {
232
		return $this->log()->content();
233
	}
234
235
	public function getDescription() {
236
		$envName = $this->Environment()->getFullName();
237
		if($this->Direction == 'get') {
238
			if($this->Origin == 'ManualUpload') {
239
				$description = 'Manual upload of ' . $this->getModeNice() . ' to ' . $envName;
240
			} elseif($this->IsBackupDataTransfer()) {
241
				$description = 'Automated backup of ' . $this->getModeNice() . ' from ' . $envName;
242
			} else {
243
				$description = 'Backup of ' . $this->getModeNice() . ' to ' . $envName;
244
			}
245
		} else {
246
			$description = 'Restore ' . $this->getModeNice() . ' to ' . $envName;
247
		}
248
249
		return $description;
250
	}
251
252
	public function getModeNice() {
253
		if($this->Mode == 'all') {
254
			return 'database and assets';
255
		} else {
256
			return $this->Mode;
257
		}
258
	}
259
260
	/**
261
	 * Is this transfer an automated backup prior to a push transfer or deployment?
262
	 * @return boolean
263
	 */
264
	public function IsBackupDataTransfer() {
265
		$deploymentBackup = DB::query(sprintf(
266
			'SELECT COUNT("ID") FROM "DNDeployment" WHERE "BackupDataTransferID" = %d',
267
			$this->ID
268
		))->value();
269
		if ($deploymentBackup) {
270
			return true;
271
		}
272
273
		$transferBackup = DB::query(sprintf(
274
			'SELECT COUNT("ID") FROM "DNDataTransfer" WHERE "BackupDataTransferID" = %d',
275
			$this->ID
276
		))->value();
277
		if ($transferBackup) {
278
			return true;
279
		}
280
281
		return false;
282
	}
283
284
	/**
285
	 * Returns the status of the resque job
286
	 *
287
	 * @return string
288
	 */
289 View Code Duplication
	public function ResqueStatus() {
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...
290
		$status = new Resque_Job_Status($this->ResqueToken);
0 ignored issues
show
Security Bug introduced by
It seems like $this->ResqueToken can also be of type false; however, Resque_Job_Status::__construct() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
291
		$statusCode = $status->get();
292
		// The Resque job can no longer be found, fallback to the DNDataTransfer.Status
293
		if($statusCode === false) {
294
			// Translate from the DNDataTransfer.Status to the Resque job status for UI purposes
295
			switch($this->Status) {
296
				case 'Finished':
297
					return 'Complete';
298
				case 'Started':
299
					return 'Running';
300
				default:
301
					return $this->Status;
302
			}
303
		}
304
		return self::map_resque_status($statusCode);
305
	}
306
307
}
308