Local   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 499
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 62
lcom 1
cbo 7
dl 0
loc 499
ccs 192
cts 192
cp 1
rs 3.44
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 4
A ensureDirectory() 0 17 5
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 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
    /**
61
     * @var int
62
     */
63
    private $linkHandling;
64
65
    /**
66
     * Constructor.
67
     *
68
     * @param string $root
69
     * @param int    $writeFlags
70
     * @param int    $linkHandling
71
     * @param array  $permissions
72
     *
73
     * @throws LogicException
74
     */
75 177
    public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = [])
76
    {
77 177
        $root = is_link($root) ? realpath($root) : $root;
78 177
        $this->permissionMap = array_replace_recursive(static::$permissions, $permissions);
79 177
        $this->ensureDirectory($root);
80
81 177
        if ( ! is_dir($root) || ! is_readable($root)) {
82 3
            throw new LogicException('The root path ' . $root . ' is not readable.');
83
        }
84
85 177
        $this->setPathPrefix($root);
86 177
        $this->writeFlags = $writeFlags;
87 177
        $this->linkHandling = $linkHandling;
88 177
    }
89
90
    /**
91
     * Ensure the root directory exists.
92
     *
93
     * @param string $root root directory path
94
     *
95
     * @return void
96
     *
97
     * @throws Exception in case the root directory can not be created
98
     */
99 177
    protected function ensureDirectory($root)
100
    {
101 177
        if ( ! is_dir($root)) {
102 21
            $umask = umask(0);
103
104 21
            if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) {
105 3
                $mkdirError = error_get_last();
106 1
            }
107
108 21
            umask($umask);
109
110 21
            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 6
        }
115 177
    }
116
117
    /**
118
     * @inheritdoc
119
     */
120 75
    public function has($path)
121
    {
122 75
        $location = $this->applyPathPrefix($path);
123
124 75
        return file_exists($location);
125
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130 108
    public function write($path, $contents, Config $config)
131
    {
132 108
        $location = $this->applyPathPrefix($path);
133 108
        $this->ensureDirectory(dirname($location));
134
135 108
        if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
136 3
            return false;
137
        }
138
139 105
        $type = 'file';
140 105
        $result = compact('contents', 'type', 'size', 'path');
141
142 105
        if ($visibility = $config->get('visibility')) {
143 3
            $result['visibility'] = $visibility;
144 3
            $this->setVisibility($path, $visibility);
145 1
        }
146
147 105
        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 = 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 21
    public function listContents($directory = '', $recursive = false)
270
    {
271 21
        $result = [];
272 21
        $location = $this->applyPathPrefix($directory);
273
274 21
        if ( ! is_dir($location)) {
275 3
            return [];
276
        }
277
278 18
        $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location);
279
280 18
        foreach ($iterator as $file) {
281 18
            $path = $this->getFilePath($file);
282
283 18
            if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) {
284 15
                continue;
285
            }
286
287 18
            $result[] = $this->normalizeFileInfo($file);
288 6
        }
289
290 15
        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
        $info = new SplFileInfo($location);
300
301 45
        return $this->normalizeFileInfo($info);
302
    }
303
304
    /**
305
     * @inheritdoc
306
     */
307 6
    public function getSize($path)
308
    {
309 6
        return $this->getMetadata($path);
310
    }
311
312
    /**
313
     * @inheritdoc
314
     */
315 9
    public function getMimetype($path)
316
    {
317 9
        $location = $this->applyPathPrefix($path);
318 9
        $finfo = new Finfo(FILEINFO_MIME_TYPE);
319 9
        $mimetype = $finfo->file($location);
320
321 9
        if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty'])) {
322 3
            $mimetype = Util\MimeType::detectByFilename($location);
323 1
        }
324
325 9
        return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype];
326
    }
327
328
    /**
329
     * @inheritdoc
330
     */
331 6
    public function getTimestamp($path)
332
    {
333 6
        return $this->getMetadata($path);
334
    }
335
336
    /**
337
     * @inheritdoc
338
     */
339 18
    public function getVisibility($path)
340
    {
341 18
        $location = $this->applyPathPrefix($path);
342 18
        clearstatcache(false, $location);
343 18
        $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4));
344 18
        $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
345
346 18
        return compact('path', 'visibility');
347
    }
348
349
    /**
350
     * @inheritdoc
351
     */
352 21
    public function setVisibility($path, $visibility)
353
    {
354 21
        $location = $this->applyPathPrefix($path);
355 21
        $type = is_dir($location) ? 'dir' : 'file';
356 21
        $success = chmod($location, $this->permissionMap[$type][$visibility]);
357
358 21
        if ($success === false) {
359 3
            return false;
360
        }
361
362 18
        return compact('path', 'visibility');
363
    }
364
365
    /**
366
     * @inheritdoc
367
     */
368 75
    public function createDir($dirname, Config $config)
369
    {
370 75
        $location = $this->applyPathPrefix($dirname);
371 75
        $umask = umask(0);
372 75
        $visibility = $config->get('visibility', 'public');
373
374 75
        if ( ! is_dir($location) && ! mkdir($location, $this->permissionMap['dir'][$visibility], true)) {
375 3
            $return = false;
376 1
        } else {
377 72
            $return = ['path' => $dirname, 'type' => 'dir'];
378
        }
379
380 75
        umask($umask);
381
382 75
        return $return;
383
    }
384
385
    /**
386
     * @inheritdoc
387
     */
388 75
    public function deleteDir($dirname)
389
    {
390 75
        $location = $this->applyPathPrefix($dirname);
391
392 75
        if ( ! is_dir($location)) {
393 57
            return false;
394
        }
395
396 72
        $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST);
397
398
        /** @var SplFileInfo $file */
399 72
        foreach ($contents as $file) {
400 27
            $this->guardAgainstUnreadableFileInfo($file);
401 27
            $this->deleteFileInfoObject($file);
402 24
        }
403
404 72
        return rmdir($location);
405
    }
406
407
    /**
408
     * @param SplFileInfo $file
409
     */
410 27
    protected function deleteFileInfoObject(SplFileInfo $file)
411
    {
412 27
        switch ($file->getType()) {
413 27
            case 'dir':
414 3
                rmdir($file->getRealPath());
415 3
                break;
416 27
            case 'link':
417 3
                unlink($file->getPathname());
418 3
                break;
419 8
            default:
420 24
                unlink($file->getRealPath());
421 9
        }
422 27
    }
423
424
    /**
425
     * Normalize the file info.
426
     *
427
     * @param SplFileInfo $file
428
     *
429
     * @return array|void
430
     *
431
     * @throws NotSupportedException
432
     */
433 63
    protected function normalizeFileInfo(SplFileInfo $file)
434
    {
435 63
        if ( ! $file->isLink()) {
436 63
            return $this->mapFileInfo($file);
437
        }
438
439 6
        if ($this->linkHandling & self::DISALLOW_LINKS) {
440 3
            throw NotSupportedException::forLink($file);
441
        }
442 3
    }
443
444
    /**
445
     * Get the normalized path from a SplFileInfo object.
446
     *
447
     * @param SplFileInfo $file
448
     *
449
     * @return string
450
     */
451 63
    protected function getFilePath(SplFileInfo $file)
452
    {
453 63
        $location = $file->getPathname();
454 63
        $path = $this->removePathPrefix($location);
455
456 63
        return trim(str_replace('\\', '/', $path), '/');
457
    }
458
459
    /**
460
     * @param string $path
461
     * @param int    $mode
462
     *
463
     * @return RecursiveIteratorIterator
464
     */
465 75
    protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST)
466
    {
467 75
        return new RecursiveIteratorIterator(
468 75
            new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
469 50
            $mode
470 25
        );
471
    }
472
473
    /**
474
     * @param string $path
475
     *
476
     * @return DirectoryIterator
477
     */
478 15
    protected function getDirectoryIterator($path)
479
    {
480 15
        $iterator = new DirectoryIterator($path);
481
482 15
        return $iterator;
483
    }
484
485
    /**
486
     * @param SplFileInfo $file
487
     *
488
     * @return array
489
     */
490 63
    protected function mapFileInfo(SplFileInfo $file)
491
    {
492
        $normalized = [
493 63
            'type' => $file->getType(),
494 63
            'path' => $this->getFilePath($file),
495 21
        ];
496
497 63
        $normalized['timestamp'] = $file->getMTime();
498
499 63
        if ($normalized['type'] === 'file') {
500 63
            $normalized['size'] = $file->getSize();
501 21
        }
502
503 63
        return $normalized;
504
    }
505
506
    /**
507
     * @param SplFileInfo $file
508
     *
509
     * @throws UnreadableFileException
510
     */
511 30
    protected function guardAgainstUnreadableFileInfo(SplFileInfo $file)
512
    {
513 30
        if ( ! $file->isReadable()) {
514 3
            throw UnreadableFileException::forFileInfo($file);
515
        }
516 27
    }
517
}
518