Local   D
last analyzed

Complexity

Total Complexity 59

Size/Duplication

Total Lines 500
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 59
lcom 1
cbo 7
dl 0
loc 500
ccs 192
cts 192
cp 1
rs 4.08
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 4
A has() 0 6 1
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 getMetadata() 0 7 1
A getSize() 0 4 1
A getTimestamp() 0 4 1
A getVisibility() 0 9 2
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 12 3
A write() 0 19 3
A writeStream() 0 27 4
A update() 0 19 3
A listContents() 0 23 5
A getMimetype() 0 12 2
A createDir() 0 16 3
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\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 6
        }
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 1
        }
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) {
154 3
            return false;
155
        }
156
157 18
        stream_copy_to_stream($resource, $stream);
158
159 18
        if ( ! fclose($stream)) {
160 3
            return false;
161
        }
162
163 15
        $type = 'file';
164
165 15
        $result = compact('type', 'path');
166
167 15
        if ($visibility = $config->get('visibility')) {
168 3
            $this->setVisibility($path, $visibility);
169 3
            $result['visibility'] = $visibility;
170 1
        }
171
172 15
        return $result;
173
    }
174
175
    /**
176
     * @inheritdoc
177
     */
178 6
    public function readStream($path)
179
    {
180 6
        $location = $this->applyPathPrefix($path);
181 6
        $stream = fopen($location, 'rb');
182
183 6
        return ['type' => 'file', 'path' => $path, 'stream' => $stream];
184
    }
185
186
    /**
187
     * @inheritdoc
188
     */
189 9
    public function updateStream($path, $resource, Config $config)
190
    {
191 9
        return $this->writeStream($path, $resource, $config);
192
    }
193
194
    /**
195
     * @inheritdoc
196
     */
197 9
    public function update($path, $contents, Config $config)
198
    {
199 9
        $location = $this->applyPathPrefix($path);
200 9
        $size = file_put_contents($location, $contents, $this->writeFlags);
201
202 9
        if ($size === false) {
203 3
            return false;
204
        }
205
206 6
        $type = 'file';
207
208 6
        $result = compact('type', 'path', 'size', 'contents');
209
210 6
        if ($mimetype = Util::guessMimeType($path, $contents)) {
211 6
            $result['mimetype'] = $mimetype;
212 2
        }
213
214 6
        return $result;
215
    }
216
217
    /**
218
     * @inheritdoc
219
     */
220 27
    public function read($path)
221
    {
222 27
        $location = $this->applyPathPrefix($path);
223 27
        $contents = file_get_contents($location);
224
225 27
        if ($contents === false) {
226 3
            return false;
227
        }
228
229 24
        return ['type' => 'file', 'path' => $path, 'contents' => $contents];
230
    }
231
232
    /**
233
     * @inheritdoc
234
     */
235 6
    public function rename($path, $newpath)
236
    {
237 6
        $location = $this->applyPathPrefix($path);
238 6
        $destination = $this->applyPathPrefix($newpath);
239 6
        $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath));
240 6
        $this->ensureDirectory($parentDirectory);
241
242 6
        return rename($location, $destination);
243
    }
244
245
    /**
246
     * @inheritdoc
247
     */
248 6
    public function copy($path, $newpath)
249
    {
250 6
        $location = $this->applyPathPrefix($path);
251 6
        $destination = $this->applyPathPrefix($newpath);
252 6
        $this->ensureDirectory(dirname($destination));
253
254 6
        return copy($location, $destination);
255
    }
256
257
    /**
258
     * @inheritdoc
259
     */
260 72
    public function delete($path)
261
    {
262 72
        $location = $this->applyPathPrefix($path);
263
264 72
        return @unlink($location);
265
    }
266
267
    /**
268
     * @inheritdoc
269
     */
270 21
    public function listContents($directory = '', $recursive = false)
271
    {
272 21
        $result = [];
273 21
        $location = $this->applyPathPrefix($directory);
274
275 21
        if ( ! is_dir($location)) {
276 3
            return [];
277
        }
278
279 18
        $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location);
280
281 18
        foreach ($iterator as $file) {
282 18
            $path = $this->getFilePath($file);
283
284 18
            if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) {
285 15
                continue;
286
            }
287
288 18
            $result[] = $this->normalizeFileInfo($file);
289 6
        }
290
291 15
        return array_filter($result);
292
    }
293
294
    /**
295
     * @inheritdoc
296
     */
297 45
    public function getMetadata($path)
298
    {
299 45
        $location = $this->applyPathPrefix($path);
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'])) {
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 18
    public function getVisibility($path)
341
    {
342 18
        $location = $this->applyPathPrefix($path);
343 18
        clearstatcache(false, $location);
344 18
        $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4));
345 18
        $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
346
347 18
        return compact('path', 'visibility');
348
    }
349
350
    /**
351
     * @inheritdoc
352
     */
353 21
    public function setVisibility($path, $visibility)
354
    {
355 21
        $location = $this->applyPathPrefix($path);
356 21
        $type = is_dir($location) ? 'dir' : 'file';
357 21
        $success = chmod($location, $this->permissionMap[$type][$visibility]);
358
359 21
        if ($success === false) {
360 3
            return false;
361
        }
362
363 18
        return compact('path', 'visibility');
364
    }
365
366
    /**
367
     * @inheritdoc
368
     */
369 75
    public function createDir($dirname, Config $config)
370
    {
371 75
        $location = $this->applyPathPrefix($dirname);
372 75
        $umask = umask(0);
373 75
        $visibility = $config->get('visibility', 'public');
374
375 75
        if ( ! is_dir($location) && ! mkdir($location, $this->permissionMap['dir'][$visibility], true)) {
376 3
            $return = false;
377 1
        } else {
378 72
            $return = ['path' => $dirname, 'type' => 'dir'];
379
        }
380
381 75
        umask($umask);
382
383 75
        return $return;
384
    }
385
386
    /**
387
     * @inheritdoc
388
     */
389 75
    public function deleteDir($dirname)
390
    {
391 75
        $location = $this->applyPathPrefix($dirname);
392
393 75
        if ( ! is_dir($location)) {
394 57
            return false;
395
        }
396
397 72
        $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST);
398
399
        /** @var SplFileInfo $file */
400 72
        foreach ($contents as $file) {
401 27
            $this->guardAgainstUnreadableFileInfo($file);
402 27
            $this->deleteFileInfoObject($file);
403 24
        }
404
405 72
        return rmdir($location);
406
    }
407
408
    /**
409
     * @param SplFileInfo $file
410
     */
411 27
    protected function deleteFileInfoObject(SplFileInfo $file)
412
    {
413 27
        switch ($file->getType()) {
414 27
            case 'dir':
415 3
                rmdir($file->getRealPath());
416 3
                break;
417 27
            case 'link':
418 3
                unlink($file->getPathname());
419 3
                break;
420 8
            default:
421 24
                unlink($file->getRealPath());
422 9
        }
423 27
    }
424
425
    /**
426
     * Normalize the file info.
427
     *
428
     * @param SplFileInfo $file
429
     *
430
     * @return array|void
431
     *
432
     * @throws NotSupportedException
433
     */
434 63
    protected function normalizeFileInfo(SplFileInfo $file)
435
    {
436 63
        if ( ! $file->isLink()) {
437 63
            return $this->mapFileInfo($file);
438
        }
439
440 6
        if ($this->linkHandling & self::DISALLOW_LINKS) {
441 3
            throw NotSupportedException::forLink($file);
442
        }
443 3
    }
444
445
    /**
446
     * Get the normalized path from a SplFileInfo object.
447
     *
448
     * @param SplFileInfo $file
449
     *
450
     * @return string
451
     */
452 63
    protected function getFilePath(SplFileInfo $file)
453
    {
454 63
        $location = $file->getPathname();
455 63
        $path = $this->removePathPrefix($location);
456
457 63
        return trim(str_replace('\\', '/', $path), '/');
458
    }
459
460
    /**
461
     * @param string $path
462
     * @param int    $mode
463
     *
464
     * @return RecursiveIteratorIterator
465
     */
466 75
    protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST)
467
    {
468 75
        return new RecursiveIteratorIterator(
469 75
            new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
470 50
            $mode
471 25
        );
472
    }
473
474
    /**
475
     * @param string $path
476
     *
477
     * @return DirectoryIterator
478
     */
479 15
    protected function getDirectoryIterator($path)
480
    {
481 15
        $iterator = new DirectoryIterator($path);
482
483 15
        return $iterator;
484
    }
485
486
    /**
487
     * @param SplFileInfo $file
488
     *
489
     * @return array
490
     */
491 63
    protected function mapFileInfo(SplFileInfo $file)
492
    {
493
        $normalized = [
494 63
            'type' => $file->getType(),
495 63
            'path' => $this->getFilePath($file),
496 21
        ];
497
498 63
        $normalized['timestamp'] = $file->getMTime();
499
500 63
        if ($normalized['type'] === 'file') {
501 63
            $normalized['size'] = $file->getSize();
502 21
        }
503
504 63
        return $normalized;
505
    }
506
507
    /**
508
     * @param SplFileInfo $file
509
     *
510
     * @throws UnreadableFileException
511
     */
512 30
    protected function guardAgainstUnreadableFileInfo(SplFileInfo $file)
513
    {
514 30
        if ( ! $file->isReadable()) {
515 3
            throw UnreadableFileException::forFileInfo($file);
516
        }
517 27
    }
518
}
519