Local   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 493
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 60
lcom 1
cbo 7
dl 0
loc 493
ccs 190
cts 190
cp 1
rs 3.6
c 0
b 0
f 0

28 Methods

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