Local   C
last analyzed

Coupling/Cohesion

Components 1
Dependencies 7

Complexity

Total Complexity 58

Size/Duplication

Total Lines 492
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 58
lcom 1
cbo 7
dl 0
loc 492
ccs 187
cts 187
cp 1
rs 6.3005
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A has() 0 6 1
A write() 0 19 3
A readStream() 0 7 1
A updateStream() 0 4 1
A __construct() 0 14 4
A ensureDirectory() 0 12 3
B writeStream() 0 24 4
A update() 0 14 2
A read() 0 11 2
A rename() 0 9 1
A copy() 0 8 1
A delete() 0 6 1
B 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 getDirectoryIterator() 0 6 1
A mapFileInfo() 0 15 2
A guardAgainstUnreadableFileInfo() 0 6 2
A getRecursiveDirectoryIterator() 0 7 1

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