Completed
Branch dbal-improvement (e43d29)
by Anton
06:02
created

FileManager::ensureDirectory()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 29
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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