Rsync   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 440
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 3

Importance

Changes 0
Metric Value
wmc 42
lcom 3
cbo 3
dl 0
loc 440
rs 9.0399
c 0
b 0
f 0

36 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 4 1
A __construct() 0 4 1
A fromPath() 0 6 1
A toPath() 0 6 1
A fromUser() 0 5 1
A fromHost() 0 5 1
A toUser() 0 5 1
A toHost() 0 5 1
A progress() 0 6 1
A stats() 0 6 1
A recursive() 0 6 1
A verbose() 0 6 1
A checksum() 0 6 1
A archive() 0 6 1
A compress() 0 6 1
A owner() 0 6 1
A group() 0 6 1
A times() 0 6 1
A delete() 0 6 1
A timeout() 0 6 1
A humanReadable() 0 6 1
A wholeFile() 0 6 1
A dryRun() 0 6 1
A itemizeChanges() 0 6 1
A excludeVcs() 0 8 1
A exclude() 0 4 1
A excludeFrom() 0 8 2
A includeFilter() 0 4 1
A filter() 0 4 1
A filesFrom() 0 8 2
A remoteShell() 0 6 1
A run() 0 6 1
A getCommand() 0 9 2
A getFromPathSpec() 0 4 1
A getToPathSpec() 0 4 1
A getPathSpec() 0 12 4

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Robo\Task\Remote;
4
5
use Robo\Contract\CommandInterface;
6
use Robo\Task\BaseTask;
7
use Robo\Exception\TaskException;
8
9
/**
10
 * Executes rsync in a flexible manner.
11
 *
12
 * ``` php
13
 * $this->taskRsync()
14
 *   ->fromPath('src/')
15
 *   ->toHost('localhost')
16
 *   ->toUser('dev')
17
 *   ->toPath('/var/www/html/app/')
18
 *   ->remoteShell('ssh -i public_key')
19
 *   ->recursive()
20
 *   ->excludeVcs()
21
 *   ->checksum()
22
 *   ->wholeFile()
23
 *   ->verbose()
24
 *   ->progress()
25
 *   ->humanReadable()
26
 *   ->stats()
27
 *   ->run();
28
 * ```
29
 *
30
 * You could also clone the task and do a dry-run first:
31
 *
32
 * ``` php
33
 * $rsync = $this->taskRsync()
34
 *   ->fromPath('src/')
35
 *   ->toPath('example.com:/var/www/html/app/')
36
 *   ->archive()
37
 *   ->excludeVcs()
38
 *   ->progress()
39
 *   ->stats();
40
 *
41
 * $dryRun = clone $rsync;
42
 * $dryRun->dryRun()->run();
43
 * if ('y' === $this->ask('Do you want to run (y/n)')) {
44
 *   $rsync->run();
45
 * }
46
 * ```
47
 */
48
class Rsync extends BaseTask implements CommandInterface
49
{
50
    use \Robo\Common\ExecOneCommand;
51
52
    /**
53
     * @var string
54
     */
55
    protected $command;
56
57
    /**
58
     * @var string
59
     */
60
    protected $fromUser;
61
62
    /**
63
     * @var string
64
     */
65
    protected $fromHost;
66
67
    /**
68
     * @var string
69
     */
70
    protected $fromPath;
71
72
    /**
73
     * @var string
74
     */
75
    protected $toUser;
76
77
    /**
78
     * @var string
79
     */
80
    protected $toHost;
81
82
    /**
83
     * @var string
84
     */
85
    protected $toPath;
86
87
    /**
88
     * @return static
89
     */
90
    public static function init()
91
    {
92
        return new static();
93
    }
94
95
    public function __construct()
96
    {
97
        $this->command = 'rsync';
98
    }
99
100
    /**
101
     * This can either be a full rsync path spec ([email protected]:path) or just a path.
102
     * In case of the former do not specify host and user.
103
     *
104
     * @param string|array $path
105
     *
106
     * @return $this
107
     */
108
    public function fromPath($path)
109
    {
110
        $this->fromPath = $path;
0 ignored issues
show
Documentation Bug introduced by Peter Gasser
It seems like $path can also be of type array. However, the property $fromPath is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
111
112
        return $this;
113
    }
114
115
    /**
116
     * This can either be a full rsync path spec ([email protected]:path) or just a path.
117
     * In case of the former do not specify host and user.
118
     *
119
     * @param string $path
120
     *
121
     * @return $this
122
     */
123
    public function toPath($path)
124
    {
125
        $this->toPath = $path;
126
127
        return $this;
128
    }
129
130
    /**
131
     * @param string $fromUser
132
     *
133
     * @return $this
134
     */
135
    public function fromUser($fromUser)
136
    {
137
        $this->fromUser = $fromUser;
138
        return $this;
139
    }
140
141
    /**
142
     * @param string $fromHost
143
     *
144
     * @return $this
145
     */
146
    public function fromHost($fromHost)
147
    {
148
        $this->fromHost = $fromHost;
149
        return $this;
150
    }
151
152
    /**
153
     * @param string $toUser
154
     *
155
     * @return $this
156
     */
157
    public function toUser($toUser)
158
    {
159
        $this->toUser = $toUser;
160
        return $this;
161
    }
162
163
    /**
164
     * @param string $toHost
165
     *
166
     * @return $this
167
     */
168
    public function toHost($toHost)
169
    {
170
        $this->toHost = $toHost;
171
        return $this;
172
    }
173
174
    /**
175
     * @return $this
176
     */
177
    public function progress()
178
    {
179
        $this->option(__FUNCTION__);
180
181
        return $this;
182
    }
183
184
    /**
185
     * @return $this
186
     */
187
    public function stats()
188
    {
189
        $this->option(__FUNCTION__);
190
191
        return $this;
192
    }
193
194
    /**
195
     * @return $this
196
     */
197
    public function recursive()
198
    {
199
        $this->option(__FUNCTION__);
200
201
        return $this;
202
    }
203
204
    /**
205
     * @return $this
206
     */
207
    public function verbose()
208
    {
209
        $this->option(__FUNCTION__);
210
211
        return $this;
212
    }
213
214
    /**
215
     * @return $this
216
     */
217
    public function checksum()
218
    {
219
        $this->option(__FUNCTION__);
220
221
        return $this;
222
    }
223
224
    /**
225
     * @return $this
226
     */
227
    public function archive()
228
    {
229
        $this->option(__FUNCTION__);
230
231
        return $this;
232
    }
233
234
    /**
235
     * @return $this
236
     */
237
    public function compress()
238
    {
239
        $this->option(__FUNCTION__);
240
241
        return $this;
242
    }
243
244
    /**
245
     * @return $this
246
     */
247
    public function owner()
248
    {
249
        $this->option(__FUNCTION__);
250
251
        return $this;
252
    }
253
254
    /**
255
     * @return $this
256
     */
257
    public function group()
258
    {
259
        $this->option(__FUNCTION__);
260
261
        return $this;
262
    }
263
264
    /**
265
     * @return $this
266
     */
267
    public function times()
268
    {
269
        $this->option(__FUNCTION__);
270
271
        return $this;
272
    }
273
274
    /**
275
     * @return $this
276
     */
277
    public function delete()
278
    {
279
        $this->option(__FUNCTION__);
280
281
        return $this;
282
    }
283
284
    /**
285
     * @param int $seconds
286
     *
287
     * @return $this
288
     */
289
    public function timeout($seconds)
290
    {
291
        $this->option(__FUNCTION__, $seconds);
292
293
        return $this;
294
    }
295
296
    /**
297
     * @return $this
298
     */
299
    public function humanReadable()
300
    {
301
        $this->option('human-readable');
302
303
        return $this;
304
    }
305
306
    /**
307
     * @return $this
308
     */
309
    public function wholeFile()
310
    {
311
        $this->option('whole-file');
312
313
        return $this;
314
    }
315
316
    /**
317
     * @return $this
318
     */
319
    public function dryRun()
320
    {
321
        $this->option('dry-run');
322
323
        return $this;
324
    }
325
326
    /**
327
     * @return $this
328
     */
329
    public function itemizeChanges()
330
    {
331
        $this->option('itemize-changes');
332
333
        return $this;
334
    }
335
336
    /**
337
     * Excludes .git, .svn and .hg items at any depth.
338
     *
339
     * @return $this
340
     */
341
    public function excludeVcs()
342
    {
343
        return $this->exclude([
344
            '.git',
345
            '.svn',
346
            '.hg',
347
        ]);
348
    }
349
350
    /**
351
     * @param array|string $pattern
352
     *
353
     * @return $this
354
     */
355
    public function exclude($pattern)
356
    {
357
        return $this->optionList(__FUNCTION__, $pattern);
358
    }
359
360
    /**
361
     * @param string $file
362
     *
363
     * @return $this
364
     *
365
     * @throws \Robo\Exception\TaskException
366
     */
367
    public function excludeFrom($file)
368
    {
369
        if (!is_readable($file)) {
370
            throw new TaskException($this, "Exclude file $file is not readable");
371
        }
372
373
        return $this->option('exclude-from', $file);
374
    }
375
376
    /**
377
     * @param array|string $pattern
378
     *
379
     * @return $this
380
     */
381
    public function includeFilter($pattern)
382
    {
383
        return $this->optionList('include', $pattern);
384
    }
385
386
    /**
387
     * @param array|string $pattern
388
     *
389
     * @return $this
390
     */
391
    public function filter($pattern)
392
    {
393
        return $this->optionList(__FUNCTION__, $pattern);
394
    }
395
396
    /**
397
     * @param string $file
398
     *
399
     * @return $this
400
     *
401
     * @throws \Robo\Exception\TaskException
402
     */
403
    public function filesFrom($file)
404
    {
405
        if (!is_readable($file)) {
406
            throw new TaskException($this, "Files-from file $file is not readable");
407
        }
408
409
        return $this->option('files-from', $file);
410
    }
411
412
    /**
413
     * @param string $command
414
     *
415
     * @return $this
416
     */
417
    public function remoteShell($command)
418
    {
419
        $this->option('rsh', "$command");
420
421
        return $this;
422
    }
423
424
    /**
425
     * {@inheritdoc}
426
     */
427
    public function run()
428
    {
429
        $command = $this->getCommand();
430
431
        return $this->executeCommand($command);
432
    }
433
434
    /**
435
     * Returns command that can be executed.
436
     * This method is used to pass generated command from one task to another.
437
     *
438
     * @return string
439
     */
440
    public function getCommand()
441
    {
442
        foreach ((array)$this->fromPath as $from) {
443
            $this->option(null, $this->getFromPathSpec($from));
444
        }
445
        $this->option(null, $this->getToPathSpec());
446
447
        return $this->command . $this->arguments;
448
    }
449
450
    /**
451
     * @param string $from
452
     *
453
     * @return string
454
     */
455
    protected function getFromPathSpec($from)
456
    {
457
        return $this->getPathSpec($this->fromHost, $this->fromUser, $from);
458
    }
459
460
    /**
461
     * @return string
462
     */
463
    protected function getToPathSpec()
464
    {
465
        return $this->getPathSpec($this->toHost, $this->toUser, $this->toPath);
466
    }
467
468
    /**
469
     * @param string $host
470
     * @param string $user
471
     * @param string $path
472
     *
473
     * @return string
474
     */
475
    protected function getPathSpec($host, $user, $path)
476
    {
477
        $spec = isset($path) ? $path : '';
478
        if (!empty($host)) {
479
            $spec = "{$host}:{$spec}";
480
        }
481
        if (!empty($user)) {
482
            $spec = "{$user}@{$spec}";
483
        }
484
485
        return $spec;
486
    }
487
}
488