FileManager   F
last analyzed

Complexity

Total Complexity 61

Size/Duplication

Total Lines 422
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Test Coverage

Coverage 91.55%

Importance

Changes 0
Metric Value
dl 0
loc 422
rs 3.52
c 0
b 0
f 0
ccs 130
cts 142
cp 0.9155
wmc 61
lcom 2
cbo 6

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B ensureDirectory() 0 37 7
A read() 0 8 2
B write() 0 35 6
A append() 0 8 1
A localFilename() 0 9 2
A delete() 0 13 2
A deleteDirectory() 0 23 5
A move() 0 8 2
A copy() 0 8 2
A touch() 0 8 2
A exists() 0 4 1
A size() 0 8 2
A extension() 0 4 1
A md5() 0 8 2
A time() 0 8 2
A isDirectory() 0 4 1
A isFile() 0 4 1
A getPermissions() 0 8 2
A setPermissions() 0 9 3
A getFiles() 0 16 3
A tempFilename() 0 16 3
A normalizePath() 0 7 2
A relativePath() 0 30 4
A __destruct() 0 6 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Spiral\Files;
10
11
use Spiral\Core\Component;
12
use Spiral\Core\Container\SingletonInterface;
13
use Spiral\Files\Exceptions\FileNotFoundException;
14
use Spiral\Files\Exceptions\FilesException;
15
use Spiral\Files\Exceptions\WriteErrorException;
16
use Symfony\Component\Finder\Finder;
17
use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
18
19
/**
20
 * Default abstraction for file management operations.
21
 */
22
class FileManager extends Component implements SingletonInterface, FilesInterface
23
{
24
    /**
25
     * Default file mode for this manager.
26
     */
27
    const DEFAULT_FILE_MODE = self::READONLY;
28
29
    /**
30
     * Files to be removed when component destructed.
31
     *
32
     * @var array
33
     */
34
    private $destructFiles = [];
35
36
    /**
37
     * FileManager constructor.
38
     */
39 68
    public function __construct()
40
    {
41 68
        register_shutdown_function([$this, '__destruct']);
42 68
    }
43
44
    /**
45
     * {@inheritdoc}
46
     *
47
     * @param bool $recursivePermissions Propagate permissions on created directories.
48
     */
49 44
    public function ensureDirectory(
50
        string $directory,
51
        int $mode = null,
52
        bool $recursivePermissions = true
53
    ): bool {
54 44
        if (empty($mode)) {
55 8
            $mode = self::DEFAULT_FILE_MODE;
56
        }
57
58
        //Directories always executable
59 44
        $mode = $mode | 0111;
60 44
        if (is_dir($directory)) {
61
            //Exists :(
62 43
            return $this->setPermissions($directory, $mode);
63
        }
64
65 8
        if (!$recursivePermissions) {
66
            return mkdir($directory, $mode, true);
67
        }
68
69 8
        $directoryChain = [basename($directory)];
70
71 8
        $baseDirectory = $directory;
72 8
        while (!is_dir($baseDirectory = dirname($baseDirectory))) {
73 5
            $directoryChain[] = basename($baseDirectory);
74
        }
75
76 8
        foreach (array_reverse($directoryChain) as $directory) {
77 8
            if (!mkdir($baseDirectory = "{$baseDirectory}/{$directory}")) {
78
                return false;
79
            }
80
81 8
            chmod($baseDirectory, $mode);
82
        }
83
84 8
        return true;
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90 25
    public function read(string $filename): string
91
    {
92 25
        if (!$this->exists($filename)) {
93 1
            throw new FileNotFoundException($filename);
94
        }
95
96 24
        return file_get_contents($filename);
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     *
102
     * @param bool $append To append data at the end of existed file.
103
     */
104 19
    public function write(
105
        string $filename,
106
        string $data,
107
        int $mode = null,
108
        bool $ensureDirectory = false,
109
        bool $append = false
110
    ): bool {
111 19
        $mode = $mode ?? self::DEFAULT_FILE_MODE;
112
113
        try {
114 19
            if ($ensureDirectory) {
115 2
                $this->ensureDirectory(dirname($filename), $mode);
116
            }
117
118 19
            if ($this->exists($filename)) {
119
                //Forcing mode for existed file
120 6
                $this->setPermissions($filename, $mode);
121
            }
122
123 19
            $result = file_put_contents(
124
                $filename,
125
                $data,
126 19
                $append ? FILE_APPEND | LOCK_EX : LOCK_EX
127
            );
128
129 19
            if ($result !== false) {
130
                //Forcing mode after file creation
131 19
                $this->setPermissions($filename, $mode);
132
            }
133
        } catch (\Exception $e) {
134
            throw new WriteErrorException($e->getMessage(), $e->getCode(), $e);
135
        }
136
137 19
        return $result;
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143 2
    public function append(
144
        string $filename,
145
        string $data,
146
        int $mode = null,
147
        bool $ensureDirectory = false
148
    ): bool {
149 2
        return $this->write($filename, $data, $mode, $ensureDirectory, true);
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 2
    public function localFilename(string $filename): string
156
    {
157 2
        if (!$this->exists($filename)) {
158 1
            throw new FileNotFoundException($filename);
159
        }
160
161
        //Since default implementation is local we are allowed to do that
162 1
        return $filename;
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 21
    public function delete(string $filename)
169
    {
170 21
        if ($this->exists($filename)) {
171 20
            $result = unlink($filename);
172
173
            //Wiping out changes in local file cache
174 20
            clearstatcache(false, $filename);
175
176 20
            return $result;
177
        }
178
179 1
        return false;
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     *
185
     * @see http://stackoverflow.com/questions/3349753/delete-directory-with-files-in-it
186
     *
187
     * @param string $directory
188
     * @param bool   $contentOnly
189
     *
190
     * @throws FilesException
191
     */
192 44
    public function deleteDirectory(string $directory, bool $contentOnly = false)
193
    {
194 44
        if (!$this->isDirectory($directory)) {
195
            throw new FilesException("Undefined or invalid directory {$directory}");
196
        }
197
198 44
        $files = new \RecursiveIteratorIterator(
199 44
            new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS),
200 44
            \RecursiveIteratorIterator::CHILD_FIRST
201
        );
202
203 44
        foreach ($files as $file) {
204 24
            if ($file->isDir()) {
205 10
                rmdir($file->getRealPath());
206
            } else {
207 24
                $this->delete($file->getRealPath());
208
            }
209
        }
210
211 44
        if (!$contentOnly) {
212 1
            rmdir($directory);
213
        }
214 44
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219 2
    public function move(string $filename, string $destination): bool
220
    {
221 2
        if (!$this->exists($filename)) {
222 1
            throw new FileNotFoundException($filename);
223
        }
224
225 1
        return rename($filename, $destination);
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231 2
    public function copy(string $filename, string $destination): bool
232
    {
233 2
        if (!$this->exists($filename)) {
234 1
            throw new FileNotFoundException($filename);
235
        }
236
237 1
        return copy($filename, $destination);
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243 2
    public function touch(string $filename, int $mode = null): bool
244
    {
245 2
        if (!touch($filename)) {
246
            return false;
247
        }
248
249 2
        return $this->setPermissions($filename, $mode ?? self::DEFAULT_FILE_MODE);
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255 63
    public function exists(string $filename): bool
256
    {
257 63
        return file_exists($filename);
258
    }
259
260
    /**
261
     * {@inheritdoc}
262
     */
263 2
    public function size(string $filename): int
264
    {
265 2
        if (!$this->exists($filename)) {
266 1
            throw new FileNotFoundException($filename);
267
        }
268
269 1
        return filesize($filename);
270
    }
271
272
    /**
273
     * {@inheritdoc}
274
     */
275 3
    public function extension(string $filename): string
276
    {
277 3
        return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
278
    }
279
280
    /**
281
     * {@inheritdoc}
282
     */
283 21
    public function md5(string $filename): string
284
    {
285 21
        if (!$this->exists($filename)) {
286 1
            throw new FileNotFoundException($filename);
287
        }
288
289 20
        return md5_file($filename);
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295 2
    public function time(string $filename): int
296
    {
297 2
        if (!$this->exists($filename)) {
298 1
            throw new FileNotFoundException($filename);
299
        }
300
301 1
        return filemtime($filename);
302
    }
303
304
    /**
305
     * {@inheritdoc}
306
     */
307 44
    public function isDirectory(string $filename): bool
308
    {
309 44
        return is_dir($filename);
310
    }
311
312
    /**
313
     * {@inheritdoc}
314
     */
315 5
    public function isFile(string $filename): bool
316
    {
317 5
        return is_file($filename);
318
    }
319
320
    /**
321
     * {@inheritdoc}
322
     */
323 43
    public function getPermissions(string $filename): int
324
    {
325 43
        if (!$this->exists($filename)) {
326
            throw new FileNotFoundException($filename);
327
        }
328
329 43
        return fileperms($filename) & 0777;
330
    }
331
332
    /**
333
     * {@inheritdoc}
334
     */
335 43
    public function setPermissions(string $filename, int $mode)
336
    {
337 43
        if (is_dir($filename)) {
338
            //Directories must always be executable (i.e. 664 for dir => 775)
339 43
            $mode |= 0111;
340
        }
341
342 43
        return $this->getPermissions($filename) == $mode || chmod($filename, $mode);
343
    }
344
345
    /**
346
     * {@inheritdoc}
347
     *
348
     * @param Finder $finder Pre-configured Finder.
349
     */
350 2
    public function getFiles(string $location, string $pattern = null, Finder $finder = null): array
351
    {
352 2
        $finder = $finder ?? new Finder();
353 2
        $finder->files()->in($location);
354
355 2
        if (!empty($pattern)) {
356 1
            $finder->name($pattern);
357
        }
358
359 2
        $result = [];
360 2
        foreach ($finder->getIterator() as $file) {
361 1
            $result[] = $this->normalizePath((string)$file);
362
        }
363
364 2
        return $result;
365
    }
366
367
    /**
368
     * {@inheritdoc}
369
     */
370 4
    public function tempFilename(string $extension = '', string $location = null): string
371
    {
372 4
        if (empty($location)) {
373 3
            $location = sys_get_temp_dir();
374
        }
375
376 4
        $filename = tempnam($location, 'spiral');
377
378 4
        if (!empty($extension)) {
379
            //I should find more original way of doing that
380 3
            rename($filename, $filename = "{$filename}.{$extension}");
381 3
            $this->destructFiles[] = $filename;
382
        }
383
384 4
        return $filename;
385
    }
386
387
    /**
388
     * {@inheritdoc}
389
     */
390 24
    public function normalizePath(string $path, bool $asDirectory = false): string
391
    {
392 24
        $path = str_replace(['//', '\\'], '/', $path);
393
394
        //Potentially open links and ../ type directories?
395 24
        return rtrim($path, '/') . ($asDirectory ? '/' : '');
396
    }
397
398
    /**
399
     * {@inheritdoc}
400
     *
401
     * @link http://stackoverflow.com/questions/2637945/getting-relative-path-from-absolute-path-in-php
402
     */
403 1
    public function relativePath(string $path, string $from): string
404
    {
405 1
        $path = $this->normalizePath($path);
406 1
        $from = $this->normalizePath($from);
407
408 1
        $from = explode('/', $from);
409 1
        $path = explode('/', $path);
410 1
        $relative = $path;
411
412 1
        foreach ($from as $depth => $dir) {
413
            //Find first non-matching dir
414 1
            if ($dir === $path[$depth]) {
415
                //Ignore this directory
416 1
                array_shift($relative);
417
            } else {
418
                //Get number of remaining dirs to $from
419
                $remaining = count($from) - $depth;
420
                if ($remaining > 1) {
421
                    //Add traversals up to first matching directory
422
                    $padLength = (count($relative) + $remaining - 1) * -1;
423
                    $relative = array_pad($relative, $padLength, '..');
424
                    break;
425
                } else {
426 1
                    $relative[0] = './' . $relative[0];
427
                }
428
            }
429
        }
430
431 1
        return implode('/', $relative);
432
    }
433
434
    /**
435
     * Destruct every temporary file.
436
     */
437 1
    public function __destruct()
438
    {
439 1
        foreach ($this->destructFiles as $filename) {
440 1
            $this->delete($filename);
441
        }
442 1
    }
443
}
444