AbstractFtpAdapter   C
last analyzed

Coupling/Cohesion

Components 1
Dependencies 4

Complexity

Total Complexity 62

Size/Duplication

Total Lines 619
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 62
lcom 1
cbo 4
dl 0
loc 619
ccs 154
cts 154
cp 1
rs 5.7977
c 0
b 0
f 0

40 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A setConfig() 0 16 4
A getHost() 0 4 1
A setHost() 0 6 1
A setPermPublic() 0 6 1
A setPermPrivate() 0 6 1
A getPort() 0 4 1
A getRoot() 0 4 1
A setPort() 0 6 1
A setRoot() 0 6 1
A getUsername() 0 6 2
A setUsername() 0 6 1
A getPassword() 0 4 1
A setPassword() 0 6 1
A getTimeout() 0 4 1
A setTimeout() 0 6 1
A getSystemType() 0 4 1
A setSystemType() 0 6 1
A listContents() 0 4 1
listDirectoryContents() 0 1 ?
A normalizeListing() 0 17 3
A sortListing() 0 10 1
A normalizeObject() 0 12 4
B normalizeUnixObject() 0 22 5
B normalizeWindowsObject() 0 28 6
A detectSystemType() 0 4 2
A detectType() 0 4 2
A normalizePermissions() 0 20 1
A removeDotDirectories() 0 12 3
A has() 0 4 1
A getSize() 0 4 1
A getVisibility() 0 4 1
A ensureDirectory() 0 6 3
A getConnection() 0 12 3
connect() 0 1 ?
disconnect() 0 1 ?
isConnected() 0 1 ?
A getPermPublic() 0 4 1
A getPermPrivate() 0 4 1
A __destruct() 0 4 1

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace League\Flysystem\Adapter;
4
5
use DateTime;
6
use League\Flysystem\AdapterInterface;
7
use League\Flysystem\Config;
8
use League\Flysystem\NotSupportedException;
9
use League\Flysystem\SafeStorage;
10
use RuntimeException;
11
12
abstract class AbstractFtpAdapter extends AbstractAdapter
13
{
14
    /**
15
     * @var mixed
16
     */
17
    protected $connection;
18
19
    /**
20
     * @var string
21
     */
22
    protected $host;
23
24
    /**
25
     * @var int
26
     */
27
    protected $port = 21;
28
29
    /**
30
     * @var bool
31
     */
32
    protected $ssl = false;
33
34
    /**
35
     * @var int
36
     */
37
    protected $timeout = 90;
38
39
    /**
40
     * @var bool
41
     */
42
    protected $passive = true;
43
44
    /**
45
     * @var string
46
     */
47
    protected $separator = '/';
48
49
    /**
50
     * @var string|null
51
     */
52
    protected $root;
53
54
    /**
55
     * @var int
56
     */
57
    protected $permPublic = 0744;
58
59
    /**
60
     * @var int
61
     */
62
    protected $permPrivate = 0700;
63
64
    /**
65
     * @var array
66
     */
67
    protected $configurable = [];
68
69
    /**
70
     * @var string
71
     */
72
    protected $systemType;
73
74
    /**
75
     * @var bool
76
     */
77
    protected $alternativeRecursion = false;
78
79
    /**
80
     * @var SafeStorage
81
     */
82
    protected $safeStorage;
83
84
    /**
85
     * Constructor.
86
     *
87
     * @param array $config
88
     */
89 99
    public function __construct(array $config)
90
    {
91 99
        $this->safeStorage = new SafeStorage();
92 99
        $this->setConfig($config);
93 99
    }
94
95
    /**
96
     * Set the config.
97
     *
98
     * @param array $config
99
     *
100
     * @return $this
101
     */
102 99
    public function setConfig(array $config)
103
    {
104 99
        foreach ($this->configurable as $setting) {
105 99
            if ( ! isset($config[$setting])) {
106 99
                continue;
107
            }
108
109 99
            $method = 'set' . ucfirst($setting);
110
111 99
            if (method_exists($this, $method)) {
112 99
                $this->$method($config[$setting]);
113 99
            }
114 99
        }
115
116 99
        return $this;
117
    }
118
119
    /**
120
     * Returns the host.
121
     *
122
     * @return string
123
     */
124 93
    public function getHost()
125
    {
126 93
        return $this->host;
127
    }
128
129
    /**
130
     * Set the host.
131
     *
132
     * @param string $host
133
     *
134
     * @return $this
135
     */
136 99
    public function setHost($host)
137
    {
138 99
        $this->host = $host;
139
140 99
        return $this;
141
    }
142
143
    /**
144
     * Set the public permission value.
145
     *
146
     * @param int $permPublic
147
     *
148
     * @return $this
149
     */
150 81
    public function setPermPublic($permPublic)
151
    {
152 81
        $this->permPublic = $permPublic;
153
154 81
        return $this;
155
    }
156
157
    /**
158
     * Set the private permission value.
159
     *
160
     * @param int $permPrivate
161
     *
162
     * @return $this
163
     */
164 81
    public function setPermPrivate($permPrivate)
165
    {
166 81
        $this->permPrivate = $permPrivate;
167
168 81
        return $this;
169
    }
170
171
    /**
172
     * Returns the ftp port.
173
     *
174
     * @return int
175
     */
176 93
    public function getPort()
177
    {
178 93
        return $this->port;
179
    }
180
181
    /**
182
     * Returns the root folder to work from.
183
     *
184
     * @return string
185
     */
186 81
    public function getRoot()
187
    {
188 81
        return $this->root;
189
    }
190
191
    /**
192
     * Set the ftp port.
193
     *
194
     * @param int|string $port
195
     *
196
     * @return $this
197
     */
198 81
    public function setPort($port)
199
    {
200 81
        $this->port = (int) $port;
201
202 81
        return $this;
203
    }
204
205
    /**
206
     * Set the root folder to work from.
207
     *
208
     * @param string $root
209
     *
210
     * @return $this
211
     */
212 90
    public function setRoot($root)
213
    {
214 90
        $this->root = rtrim($root, '\\/') . $this->separator;
215
216 90
        return $this;
217
    }
218
219
    /**
220
     * Returns the ftp username.
221
     *
222
     * @return string username
223
     */
224 87
    public function getUsername()
225
    {
226 87
        $username = $this->safeStorage->retrieveSafely('username');
227
228 87
        return $username !== null ? $username : 'anonymous';
229
    }
230
231
    /**
232
     * Set ftp username.
233
     *
234
     * @param string $username
235
     *
236
     * @return $this
237
     */
238 81
    public function setUsername($username)
239
    {
240 81
        $this->safeStorage->storeSafely('username', $username);
241
242 81
        return $this;
243
    }
244
245
    /**
246
     * Returns the password.
247
     *
248
     * @return string password
249
     */
250 87
    public function getPassword()
251
    {
252 87
        return $this->safeStorage->retrieveSafely('password');
253
    }
254
255
    /**
256
     * Set the ftp password.
257
     *
258
     * @param string $password
259
     *
260
     * @return $this
261
     */
262 81
    public function setPassword($password)
263
    {
264 81
        $this->safeStorage->storeSafely('password', $password);
265
266 81
        return $this;
267
    }
268
269
    /**
270
     * Returns the amount of seconds before the connection will timeout.
271
     *
272
     * @return int
273
     */
274 93
    public function getTimeout()
275
    {
276 93
        return $this->timeout;
277
    }
278
279
    /**
280
     * Set the amount of seconds before the connection should timeout.
281
     *
282
     * @param int $timeout
283
     *
284
     * @return $this
285
     */
286 81
    public function setTimeout($timeout)
287
    {
288 81
        $this->timeout = (int) $timeout;
289
290 81
        return $this;
291
    }
292
293
    /**
294
     * Return the FTP system type.
295
     *
296
     * @return string
297
     */
298 3
    public function getSystemType()
299
    {
300 3
        return $this->systemType;
301
    }
302
303
    /**
304
     * Set the FTP system type (windows or unix).
305
     *
306
     * @param string $systemType
307
     *
308
     * @return $this
309
     */
310 15
    public function setSystemType($systemType)
311
    {
312 15
        $this->systemType = strtolower($systemType);
313
314 15
        return $this;
315
    }
316
317
    /**
318
     * @inheritdoc
319
     */
320 27
    public function listContents($directory = '', $recursive = false)
321
    {
322 27
        return $this->listDirectoryContents($directory, $recursive);
323
    }
324
325
    abstract protected function listDirectoryContents($directory, $recursive = false);
326
327
    /**
328
     * Normalize a directory listing.
329
     *
330
     * @param array  $listing
331
     * @param string $prefix
332
     *
333
     * @return array directory listing
334
     */
335 21
    protected function normalizeListing(array $listing, $prefix = '')
336
    {
337 21
        $base = $prefix;
338 21
        $result = [];
339 21
        $listing = $this->removeDotDirectories($listing);
340
341 21
        while ($item = array_shift($listing)) {
342 21
            if (preg_match('#^.*:$#', $item)) {
343 9
                $base = trim($item, ':');
344 9
                continue;
345
            }
346
347 21
            $result[] = $this->normalizeObject($item, $base);
348 18
        }
349
350 18
        return $this->sortListing($result);
351
    }
352
353
    /**
354
     * Sort a directory listing.
355
     *
356
     * @param array $result
357
     *
358
     * @return array sorted listing
359
     */
360 18
    protected function sortListing(array $result)
361
    {
362
        $compare = function ($one, $two) {
363 12
            return strnatcmp($one['path'], $two['path']);
364 18
        };
365
366 18
        usort($result, $compare);
367
368 18
        return $result;
369
    }
370
371
    /**
372
     * Normalize a file entry.
373
     *
374
     * @param string $item
375
     * @param string $base
376
     *
377
     * @return array normalized file array
378
     *
379
     * @throws NotSupportedException
380
     */
381 42
    protected function normalizeObject($item, $base)
382
    {
383 42
        $systemType = $this->systemType ?: $this->detectSystemType($item);
384
385 42
        if ($systemType === 'unix') {
386 33
            return $this->normalizeUnixObject($item, $base);
387 9
        } elseif ($systemType === 'windows') {
388 6
            return $this->normalizeWindowsObject($item, $base);
389
        }
390
391 3
        throw NotSupportedException::forFtpSystemType($systemType);
392
    }
393
394
    /**
395
     * Normalize a Unix file entry.
396
     *
397
     * @param string $item
398
     * @param string $base
399
     *
400
     * @return array normalized file array
401
     */
402 33
    protected function normalizeUnixObject($item, $base)
403
    {
404 33
        $item = preg_replace('#\s+#', ' ', trim($item), 7);
405
406 33
        if (count(explode(' ', $item, 9)) !== 9) {
407 3
            throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
408
        }
409
410 30
        list($permissions, /* $number */, /* $owner */, /* $group */, $size, /* $month */, /* $day */, /* $time*/, $name) = explode(' ', $item, 9);
411 30
        $type = $this->detectType($permissions);
412 30
        $path = empty($base) ? $name : $base . $this->separator . $name;
413
414 30
        if ($type === 'dir') {
415 12
            return compact('type', 'path');
416
        }
417
418 30
        $permissions = $this->normalizePermissions($permissions);
419 30
        $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
420 30
        $size = (int) $size;
421
422 30
        return compact('type', 'path', 'visibility', 'size');
423
    }
424
425
    /**
426
     * Normalize a Windows/DOS file entry.
427
     *
428
     * @param string $item
429
     * @param string $base
430
     *
431
     * @return array normalized file array
432
     */
433 6
    protected function normalizeWindowsObject($item, $base)
434
    {
435 6
        $item = preg_replace('#\s+#', ' ', trim($item), 3);
436
437 6
        if (count(explode(' ', $item, 4)) !== 4) {
438 3
            throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts.");
439
        }
440
441 3
        list($date, $time, $size, $name) = explode(' ', $item, 4);
442 3
        $path = empty($base) ? $name : $base . $this->separator . $name;
443
444
        // Check for the correct date/time format
445 3
        $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
446 3
        $dt = DateTime::createFromFormat($format, $date . $time);
447 3
        $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time");
448
449 3
        if ($size === '<DIR>') {
450 3
            $type = 'dir';
451
452 3
            return compact('type', 'path', 'timestamp');
453
        }
454
455 3
        $type = 'file';
456 3
        $visibility = AdapterInterface::VISIBILITY_PUBLIC;
457 3
        $size = (int) $size;
458
459 3
        return compact('type', 'path', 'visibility', 'size', 'timestamp');
460
    }
461
462
    /**
463
     * Get the system type from a listing item.
464
     *
465
     * @param string $item
466
     *
467
     * @return string the system type
468
     */
469 33
    protected function detectSystemType($item)
470
    {
471 33
        return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix';
472
    }
473
474
    /**
475
     * Get the file type from the permissions.
476
     *
477
     * @param string $permissions
478
     *
479
     * @return string file type
480
     */
481 30
    protected function detectType($permissions)
482
    {
483 30
        return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file';
484
    }
485
486
    /**
487
     * Normalize a permissions string.
488
     *
489
     * @param string $permissions
490
     *
491
     * @return int
492
     */
493 30
    protected function normalizePermissions($permissions)
494
    {
495
        // remove the type identifier
496 30
        $permissions = substr($permissions, 1);
497
498
        // map the string rights to the numeric counterparts
499 30
        $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1'];
500 30
        $permissions = strtr($permissions, $map);
501
502
        // split up the permission groups
503 30
        $parts = str_split($permissions, 3);
504
505
        // convert the groups
506
        $mapper = function ($part) {
507 30
            return array_sum(str_split($part));
508 30
        };
509
510
        // converts to decimal number
511 30
        return octdec(implode('', array_map($mapper, $parts)));
512
    }
513
514
    /**
515
     * Filter out dot-directories.
516
     *
517
     * @param array $list
518
     *
519
     * @return array
520
     */
521
    public function removeDotDirectories(array $list)
522
    {
523 21
        $filter = function ($line) {
524 21
            if ( ! empty($line) && ! preg_match('#.* \.(\.)?$|^total#', $line)) {
525 21
                return true;
526
            }
527
528 18
            return false;
529 21
        };
530
531 21
        return array_filter($list, $filter);
532
    }
533
534
    /**
535
     * @inheritdoc
536
     */
537 9
    public function has($path)
538
    {
539 9
        return $this->getMetadata($path);
540
    }
541
542
    /**
543
     * @inheritdoc
544
     */
545 6
    public function getSize($path)
546
    {
547 6
        return $this->getMetadata($path);
548
    }
549
550
    /**
551
     * @inheritdoc
552
     */
553 6
    public function getVisibility($path)
554
    {
555 6
        return $this->getMetadata($path);
556
    }
557
558
    /**
559
     * Ensure a directory exists.
560
     *
561
     * @param string $dirname
562
     */
563 9
    public function ensureDirectory($dirname)
564
    {
565 9
        if ( ! empty($dirname) && ! $this->has($dirname)) {
566 3
            $this->createDir($dirname, new Config());
567 3
        }
568 9
    }
569
570
    /**
571
     * @return mixed
572
     */
573 66
    public function getConnection()
574
    {
575 66
        $tries = 0;
576
577 66
        while ( ! $this->isConnected() && $tries < 3) {
578 66
            $tries++;
579 66
            $this->disconnect();
580 66
            $this->connect();
581 66
        }
582
583 66
        return $this->connection;
584
    }
585
586
    /**
587
     * Get the public permission value.
588
     *
589
     * @return int
590
     */
591 6
    public function getPermPublic()
592
    {
593 6
        return $this->permPublic;
594
    }
595
596
    /**
597
     * Get the private permission value.
598
     *
599
     * @return int
600
     */
601 6
    public function getPermPrivate()
602
    {
603 6
        return $this->permPrivate;
604
    }
605
606
    /**
607
     * Disconnect on destruction.
608
     */
609 99
    public function __destruct()
610
    {
611 99
        $this->disconnect();
612 99
    }
613
614
    /**
615
     * Establish a connection.
616
     */
617
    abstract public function connect();
618
619
    /**
620
     * Close the connection.
621
     */
622
    abstract public function disconnect();
623
624
    /**
625
     * Check if a connection is active.
626
     *
627
     * @return bool
628
     */
629
    abstract public function isConnected();
630
}
631