Completed
Branch feature/pre-split (ca29cf)
by Anton
03:23
created

FileManager   C

Complexity

Total Complexity 66

Size/Duplication

Total Lines 416
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 6

Importance

Changes 0
Metric Value
dl 0
loc 416
rs 5.7474
c 0
b 0
f 0
wmc 66
lcom 2
cbo 6

25 Methods

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