AbstractFtpAdapter   F
last analyzed

Complexity

Total Complexity 61

Size/Duplication

Total Lines 612
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 61
lcom 1
cbo 4
dl 0
loc 612
ccs 155
cts 155
cp 1
rs 3.508
c 0
b 0
f 0

40 Methods

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