Completed
Push — master ( d6bf09...e12b39 )
by Anton
04:23
created

FileManager::deleteDirectory()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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