Local   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 511
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 66
lcom 1
cbo 7
dl 0
loc 511
ccs 202
cts 202
cp 1
rs 3.12
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 4
A getSize() 0 4 1
A getTimestamp() 0 4 1
A getMimetype() 0 12 2
A has() 0 6 1
A write() 0 19 3
A writeStream() 0 20 5
A readStream() 0 7 1
A updateStream() 0 4 1
A read() 0 11 2
A rename() 0 9 1
A copy() 0 8 1
A delete() 0 6 1
A listContents() 0 23 5
A getMetadata() 0 8 1
A setVisibility() 0 12 3
A normalizeFileInfo() 0 10 3
A getFilePath() 0 7 1
A getDirectoryIterator() 0 6 1
A guardAgainstUnreadableFileInfo() 0 6 2
A ensureDirectory() 0 18 5
A update() 0 19 4
A getVisibility() 0 17 4
A createDir() 0 18 4
A deleteDir() 0 18 3
A deleteFileInfoObject() 0 13 3
A getRecursiveDirectoryIterator() 0 7 1
A mapFileInfo() 0 15 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace League\Flysystem\Adapter;
4
5
use DirectoryIterator;
6
use FilesystemIterator;
7
use finfo as Finfo;
8
use League\Flysystem\Config;
9
use League\Flysystem\Exception;
10
use League\Flysystem\NotSupportedException;
11
use League\Flysystem\UnreadableFileException;
12
use League\Flysystem\Util;
13
use LogicException;
14
use RecursiveDirectoryIterator;
15
use RecursiveIteratorIterator;
16
use SplFileInfo;
17
18
class Local extends AbstractAdapter
19
{
20
    /**
21
     * @var int
22
     */
23
    const SKIP_LINKS = 0001;
24
25
    /**
26
     * @var int
27
     */
28
    const DISALLOW_LINKS = 0002;
29
30
    /**
31
     * @var array
32
     */
33
    protected static $permissions = [
34
        'file' => [
35
            'public' => 0644,
36
            'private' => 0600,
37
        ],
38
        'dir' => [
39
            'public' => 0755,
40
            'private' => 0700,
41
        ],
42
    ];
43
44
    /**
45
     * @var string
46
     */
47
    protected $pathSeparator = DIRECTORY_SEPARATOR;
48
49
    /**
50
     * @var array
51
     */
52
    protected $permissionMap;
53
54
    /**
55
     * @var int
56
     */
57
    protected $writeFlags;
58
59
    /**
60
     * @var int
61
     */
62
    private $linkHandling;
63
64
    /**
65
     * Constructor.
66
     *
67
     * @param string $root
68
     * @param int    $writeFlags
69
     * @param int    $linkHandling
70
     * @param array  $permissions
71
     *
72
     * @throws LogicException
73
     */
74 210
    public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = [])
75
    {
76 210
        $root = is_link($root) ? realpath($root) : $root;
77 210
        $this->permissionMap = array_replace_recursive(static::$permissions, $permissions);
78 210
        $this->ensureDirectory($root);
79
80 210
        if ( ! is_dir($root) || ! is_readable($root)) {
81 3
            throw new LogicException('The root path ' . $root . ' is not readable.');
82
        }
83
84 210
        $this->setPathPrefix($root);
85 210
        $this->writeFlags = $writeFlags;
86 210
        $this->linkHandling = $linkHandling;
87 210
    }
88
89
    /**
90
     * Ensure the root directory exists.
91
     *
92
     * @param string $root root directory path
93
     *
94
     * @return void
95
     *
96
     * @throws Exception in case the root directory can not be created
97
     */
98 210
    protected function ensureDirectory($root)
99
    {
100 210
        if ( ! is_dir($root)) {
101 24
            $umask = umask(0);
102
103 24
            if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) {
104 3
                $mkdirError = error_get_last();
105 1
            }
106
107 24
            umask($umask);
108 24
            clearstatcache(false, $root);
109
110 24
            if ( ! is_dir($root)) {
111 3
                $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
112 3
                throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage));
113
            }
114 7
        }
115 210
    }
116
117
    /**
118
     * @inheritdoc
119
     */
120 78
    public function has($path)
121
    {
122 78
        $location = $this->applyPathPrefix($path);
123
124 78
        return file_exists($location);
125
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130 111
    public function write($path, $contents, Config $config)
131
    {
132 111
        $location = $this->applyPathPrefix($path);
133 111
        $this->ensureDirectory(dirname($location));
134
135 111
        if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
136 3
            return false;
137
        }
138
139 108
        $type = 'file';
140 108
        $result = compact('contents', 'type', 'size', 'path');
141
142 108
        if ($visibility = $config->get('visibility')) {
143 3
            $result['visibility'] = $visibility;
144 3
            $this->setVisibility($path, $visibility);
145 1
        }
146
147 108
        return $result;
148
    }
149
150
    /**
151
     * @inheritdoc
152
     */
153 18
    public function writeStream($path, $resource, Config $config)
154
    {
155 18
        $location = $this->applyPathPrefix($path);
156 18
        $this->ensureDirectory(dirname($location));
157 18
        $stream = fopen($location, 'w+b');
158
159 18
        if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) {
160 3
            return false;
161
        }
162
163 15
        $type = 'file';
164 15
        $result = compact('type', 'path');
165
166 15
        if ($visibility = $config->get('visibility')) {
167 3
            $this->setVisibility($path, $visibility);
168 3
            $result['visibility'] = $visibility;
169 1
        }
170
171 15
        return $result;
172
    }
173
174
    /**
175
     * @inheritdoc
176
     */
177 6
    public function readStream($path)
178
    {
179 6
        $location = $this->applyPathPrefix($path);
180 6
        $stream = fopen($location, 'rb');
181
182 6
        return ['type' => 'file', 'path' => $path, 'stream' => $stream];
183
    }
184
185
    /**
186
     * @inheritdoc
187
     */
188 9
    public function updateStream($path, $resource, Config $config)
189
    {
190 9
        return $this->writeStream($path, $resource, $config);
191
    }
192
193
    /**
194
     * @inheritdoc
195
     */
196 9
    public function update($path, $contents, Config $config)
197
    {
198 9
        $location = $this->applyPathPrefix($path);
199 9
        $size = file_put_contents($location, $contents, $this->writeFlags);
200
201 9
        if ($size === false) {
202 3
            return false;
203
        }
204
205 6
        $type = 'file';
206
207 6
        $result = compact('type', 'path', 'size', 'contents');
208
209 6
        if ($mimetype = $config->get('mimetype') ?: Util::guessMimeType($path, $contents)) {
210 6
            $result['mimetype'] = $mimetype;
211 2
        }
212
213 6
        return $result;
214
    }
215
216
    /**
217
     * @inheritdoc
218
     */
219 27
    public function read($path)
220
    {
221 27
        $location = $this->applyPathPrefix($path);
222 27
        $contents = @file_get_contents($location);
223
224 27
        if ($contents === false) {
225 3
            return false;
226
        }
227
228 24
        return ['type' => 'file', 'path' => $path, 'contents' => $contents];
229
    }
230
231
    /**
232
     * @inheritdoc
233
     */
234 6
    public function rename($path, $newpath)
235
    {
236 6
        $location = $this->applyPathPrefix($path);
237 6
        $destination = $this->applyPathPrefix($newpath);
238 6
        $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath));
239 6
        $this->ensureDirectory($parentDirectory);
240
241 6
        return rename($location, $destination);
242
    }
243
244
    /**
245
     * @inheritdoc
246
     */
247 6
    public function copy($path, $newpath)
248
    {
249 6
        $location = $this->applyPathPrefix($path);
250 6
        $destination = $this->applyPathPrefix($newpath);
251 6
        $this->ensureDirectory(dirname($destination));
252
253 6
        return copy($location, $destination);
254
    }
255
256
    /**
257
     * @inheritdoc
258
     */
259 72
    public function delete($path)
260
    {
261 72
        $location = $this->applyPathPrefix($path);
262
263 72
        return @unlink($location);
264
    }
265
266
    /**
267
     * @inheritdoc
268
     */
269 24
    public function listContents($directory = '', $recursive = false)
270
    {
271 24
        $result = [];
272 24
        $location = $this->applyPathPrefix($directory);
273
274 24
        if ( ! is_dir($location)) {
275 3
            return [];
276
        }
277
278 21
        $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location);
279
280 21
        foreach ($iterator as $file) {
281 21
            $path = $this->getFilePath($file);
282
283 21
            if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) {
284 18
                continue;
285
            }
286
287 21
            $result[] = $this->normalizeFileInfo($file);
288 7
        }
289
290 18
        return array_filter($result);
291
    }
292
293
    /**
294
     * @inheritdoc
295
     */
296 45
    public function getMetadata($path)
297
    {
298 45
        $location = $this->applyPathPrefix($path);
299 45
        clearstatcache(false, $location);
300 45
        $info = new SplFileInfo($location);
301
302 45
        return $this->normalizeFileInfo($info);
303
    }
304
305
    /**
306
     * @inheritdoc
307
     */
308 6
    public function getSize($path)
309
    {
310 6
        return $this->getMetadata($path);
311
    }
312
313
    /**
314
     * @inheritdoc
315
     */
316 9
    public function getMimetype($path)
317
    {
318 9
        $location = $this->applyPathPrefix($path);
319 9
        $finfo = new Finfo(FILEINFO_MIME_TYPE);
320 9
        $mimetype = $finfo->file($location);
321
322 9
        if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty', 'application/x-empty'])) {
323 3
            $mimetype = Util\MimeType::detectByFilename($location);
324 1
        }
325
326 9
        return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype];
327
    }
328
329
    /**
330
     * @inheritdoc
331
     */
332 6
    public function getTimestamp($path)
333
    {
334 6
        return $this->getMetadata($path);
335
    }
336
337
    /**
338
     * @inheritdoc
339
     */
340 30
    public function getVisibility($path)
341
    {
342 30
        $location = $this->applyPathPrefix($path);
343 30
        clearstatcache(false, $location);
344 30
        $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4));
345 30
        $type = is_dir($location) ? 'dir' : 'file';
346
347 30
        foreach ($this->permissionMap[$type] as $visibility => $visibilityPermissions) {
348 30
            if ($visibilityPermissions == $permissions) {
349 28
                return compact('path', 'visibility');
350
            }
351 7
        }
352
353 6
        $visibility = substr(sprintf('%o', fileperms($location)), -4);
354
355 6
        return compact('path', 'visibility');
356
    }
357
358
    /**
359
     * @inheritdoc
360
     */
361 30
    public function setVisibility($path, $visibility)
362
    {
363 30
        $location = $this->applyPathPrefix($path);
364 30
        $type = is_dir($location) ? 'dir' : 'file';
365 30
        $success = chmod($location, $this->permissionMap[$type][$visibility]);
366
367 30
        if ($success === false) {
368 3
            return false;
369
        }
370
371 27
        return compact('path', 'visibility');
372
    }
373
374
    /**
375
     * @inheritdoc
376
     */
377 84
    public function createDir($dirname, Config $config)
378
    {
379 84
        $location = $this->applyPathPrefix($dirname);
380 84
        $umask = umask(0);
381 84
        $visibility = $config->get('visibility', 'public');
382 84
        $return = ['path' => $dirname, 'type' => 'dir'];
383
384 84
        if ( ! is_dir($location)) {
385 84
            if (false === @mkdir($location, $this->permissionMap['dir'][$visibility], true)
386 84
                || false === is_dir($location)) {
387 3
                $return = false;
388 1
            }
389 28
        }
390
391 84
        umask($umask);
392
393 84
        return $return;
394
    }
395
396
    /**
397
     * @inheritdoc
398
     */
399 78
    public function deleteDir($dirname)
400
    {
401 78
        $location = $this->applyPathPrefix($dirname);
402
403 78
        if ( ! is_dir($location)) {
404 60
            return false;
405
        }
406
407 75
        $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST);
408
409
        /** @var SplFileInfo $file */
410 75
        foreach ($contents as $file) {
411 30
            $this->guardAgainstUnreadableFileInfo($file);
412 30
            $this->deleteFileInfoObject($file);
413 25
        }
414
415 75
        return rmdir($location);
416
    }
417
418
    /**
419
     * @param SplFileInfo $file
420
     */
421 30
    protected function deleteFileInfoObject(SplFileInfo $file)
422
    {
423 30
        switch ($file->getType()) {
424 30
            case 'dir':
425 3
                rmdir($file->getRealPath());
426 3
                break;
427 30
            case 'link':
428 3
                unlink($file->getPathname());
429 3
                break;
430 9
            default:
431 27
                unlink($file->getRealPath());
432 10
        }
433 30
    }
434
435
    /**
436
     * Normalize the file info.
437
     *
438
     * @param SplFileInfo $file
439
     *
440
     * @return array|void
441
     *
442
     * @throws NotSupportedException
443
     */
444 66
    protected function normalizeFileInfo(SplFileInfo $file)
445
    {
446 66
        if ( ! $file->isLink()) {
447 66
            return $this->mapFileInfo($file);
448
        }
449
450 6
        if ($this->linkHandling & self::DISALLOW_LINKS) {
451 3
            throw NotSupportedException::forLink($file);
452
        }
453 3
    }
454
455
    /**
456
     * Get the normalized path from a SplFileInfo object.
457
     *
458
     * @param SplFileInfo $file
459
     *
460
     * @return string
461
     */
462 66
    protected function getFilePath(SplFileInfo $file)
463
    {
464 66
        $location = $file->getPathname();
465 66
        $path = $this->removePathPrefix($location);
466
467 66
        return trim(str_replace('\\', '/', $path), '/');
468
    }
469
470
    /**
471
     * @param string $path
472
     * @param int    $mode
473
     *
474
     * @return RecursiveIteratorIterator
475
     */
476 78
    protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST)
477
    {
478 78
        return new RecursiveIteratorIterator(
479 78
            new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
480 52
            $mode
481 26
        );
482
    }
483
484
    /**
485
     * @param string $path
486
     *
487
     * @return DirectoryIterator
488
     */
489 18
    protected function getDirectoryIterator($path)
490
    {
491 18
        $iterator = new DirectoryIterator($path);
492
493 18
        return $iterator;
494
    }
495
496
    /**
497
     * @param SplFileInfo $file
498
     *
499
     * @return array
500
     */
501 66
    protected function mapFileInfo(SplFileInfo $file)
502
    {
503
        $normalized = [
504 66
            'type' => $file->getType(),
505 66
            'path' => $this->getFilePath($file),
506 22
        ];
507
508 66
        $normalized['timestamp'] = $file->getMTime();
509
510 66
        if ($normalized['type'] === 'file') {
511 66
            $normalized['size'] = $file->getSize();
512 22
        }
513
514 66
        return $normalized;
515
    }
516
517
    /**
518
     * @param SplFileInfo $file
519
     *
520
     * @throws UnreadableFileException
521
     */
522 33
    protected function guardAgainstUnreadableFileInfo(SplFileInfo $file)
523
    {
524 33
        if ( ! $file->isReadable()) {
525 3
            throw UnreadableFileException::forFileInfo($file);
526
        }
527 30
    }
528
}
529