Completed
Pull Request — master (#37)
by Anton
08:01
created

FileManager::deleteDirectory()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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