Passed
Pull Request — master (#407)
by Kirill
11:08 queued 03:58
created

Storage::write()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 1
b 0
f 0
nc 2
nop 4
dl 0
loc 13
rs 10
1
<?php
2
3
/**
4
 * This file is part of Spiral Framework package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Storage;
13
14
use League\Flysystem\Filesystem;
15
use League\Flysystem\FilesystemException;
16
use League\Flysystem\FilesystemOperator;
17
use Spiral\Storage\Builder\AdapterFactory;
18
use Spiral\Storage\Config\ConfigInterface;
19
use Spiral\Storage\Exception\FileOperationException;
20
use Spiral\Storage\Exception\MountException;
21
use Spiral\Storage\Exception\StorageException;
22
use Spiral\Storage\Exception\UriException;
23
use Spiral\Storage\Parser\Uri;
24
use Spiral\Storage\Parser\UriParser;
25
use Spiral\Storage\Parser\UriParserInterface;
26
27
class Storage implements StorageInterface
28
{
29
    /**
30
     * @var ConfigInterface
31
     */
32
    protected $config;
33
34
    /**
35
     * @var UriParserInterface
36
     */
37
    protected $uriParser;
38
39
    /**
40
     * @var array<string, FilesystemOperator>
41
     */
42
    protected $fileSystems = [];
43
44
    /**
45
     * @param ConfigInterface $config
46
     * @param UriParserInterface|null $parser
47
     * @throws StorageException
48
     */
49
    public function __construct(ConfigInterface $config, UriParserInterface $parser = null)
50
    {
51
        $this->config = $config;
52
        $this->uriParser = $parser ?? new UriParser();
53
54
        foreach ($config->getBucketsKeys() as $fs) {
55
            $this->mountFilesystem($fs, new Filesystem(
56
                AdapterFactory::build($this->config->buildFileSystemInfo($fs))
57
            ));
58
        }
59
    }
60
61
    /**
62
     * @inheritDoc
63
     */
64
    public function getFileSystem(string $key): FilesystemOperator
65
    {
66
        if (!$this->isFileSystemExists($key)) {
67
            throw new MountException(\sprintf('Filesystem `%s` has not been defined', $key));
68
        }
69
70
        return $this->fileSystems[$key];
71
    }
72
73
    /**
74
     * @inheritDoc
75
     */
76
    public function getFileSystemsNames(): array
77
    {
78
        return \array_keys($this->fileSystems);
79
    }
80
81
    /**
82
     * @inheritDoc
83
     */
84
    public function fileExists(string $uri): bool
85
    {
86
        /** @var FilesystemOperator $filesystem */
87
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
88
89
        try {
90
            return $filesystem->fileExists($path);
91
        } catch (FilesystemException $e) {
92
            throw new FileOperationException($e->getMessage(), $e->getCode(), $e);
93
        }
94
    }
95
96
    /**
97
     * @inheritDoc
98
     */
99
    public function read(string $uri): string
100
    {
101
        /** @var FilesystemOperator $filesystem */
102
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
103
104
        try {
105
            return $filesystem->read($path);
106
        } catch (FilesystemException $e) {
107
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
108
        }
109
    }
110
111
    /**
112
     * @inheritDoc
113
     */
114
    public function readStream(string $uri)
115
    {
116
        /** @var FilesystemOperator $filesystem */
117
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
118
119
        try {
120
            return $filesystem->readStream($path);
121
        } catch (FilesystemException $e) {
122
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
123
        }
124
    }
125
126
    /**
127
     * @inheritDoc
128
     */
129
    public function lastModified(string $uri): int
130
    {
131
        /** @var FilesystemOperator $filesystem */
132
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
133
134
        try {
135
            return $filesystem->lastModified($path);
136
        } catch (FilesystemException $e) {
137
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
138
        }
139
    }
140
141
    /**
142
     * @inheritDoc
143
     */
144
    public function fileSize(string $uri): int
145
    {
146
        /** @var FilesystemOperator $filesystem */
147
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
148
149
        try {
150
            return $filesystem->fileSize($path);
151
        } catch (FilesystemException $e) {
152
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
153
        }
154
    }
155
156
    /**
157
     * @inheritDoc
158
     */
159
    public function mimeType(string $uri): string
160
    {
161
        /** @var FilesystemOperator $filesystem */
162
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
163
164
        try {
165
            return $filesystem->mimeType($path);
166
        } catch (FilesystemException $e) {
167
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
168
        }
169
    }
170
171
    /**
172
     * @inheritDoc
173
     */
174
    public function visibility(string $uri): string
175
    {
176
        /** @var FilesystemOperator $filesystem */
177
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
178
179
        try {
180
            return $filesystem->visibility($path);
181
        } catch (FilesystemException $e) {
182
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
183
        }
184
    }
185
186
    /**
187
     * @inheritDoc
188
     */
189
    public function tempFilename(string $uri = null): string
190
    {
191
        try {
192
            $prefix = 'tmpStorageFile_';
193
194
            if ($uri !== null) {
195
                /** @var FilesystemOperator $filesystem */
196
                [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
197
                $content = $filesystem->readStream($path);
198
                $prefix = basename($uri) . '_';
199
            }
200
201
            $filePath = tempnam($this->config->getTmpDir(), $prefix);
202
203
            if (isset($content)) {
204
                file_put_contents($filePath, $content);
205
            }
206
207
            return $filePath;
208
        } catch (\Throwable $e) {
209
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
210
        }
211
    }
212
213
    /**
214
     * @inheritDoc
215
     */
216
    public function write(string $fileSystem, string $filePath, string $content, array $config = []): string
217
    {
218
        $uri = (string)Uri::create($fileSystem, $filePath);
219
220
        /** @var FilesystemOperator $filesystem */
221
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
222
223
        try {
224
            $filesystem->write($path, $content, $config);
225
226
            return $uri;
227
        } catch (FilesystemException $e) {
228
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
229
        }
230
    }
231
232
    /**
233
     * @inheritDoc
234
     */
235
    public function writeStream(string $fileSystem, string $filePath, $content, array $config = []): string
236
    {
237
        $uri = (string)Uri::create($fileSystem, $filePath);
238
239
        /** @var FilesystemOperator $filesystem */
240
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
241
242
        try {
243
            $filesystem->writeStream($path, $content, $config);
244
245
            return $uri;
246
        } catch (FilesystemException $e) {
247
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
248
        }
249
    }
250
251
    /**
252
     * @inheritDoc
253
     */
254
    public function setVisibility(string $uri, string $visibility): void
255
    {
256
        /** @var FilesystemOperator $filesystem */
257
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
258
259
        try {
260
            $filesystem->setVisibility($path, $visibility);
261
        } catch (FilesystemException $e) {
262
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
263
        }
264
    }
265
266
    /**
267
     * @inheritDoc
268
     */
269
    public function move(
270
        string $sourceUri,
271
        string $destinationFileSystem,
272
        ?string $targetFilePath = null,
273
        array $config = []
274
    ): string {
275
        /** @var FilesystemOperator $sourceFilesystem */
276
        [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($sourceUri);
277
278
        $destinationFilesystem = $this->getFileSystem($destinationFileSystem);
279
280
        try {
281
            $targetFilePath = $targetFilePath ?: $sourcePath;
282
283
            $sourceFilesystem === $destinationFilesystem
284
                ? $this->moveInTheSameFilesystem($sourceFilesystem, $sourcePath, $targetFilePath, $config)
285
                : $this->moveAcrossFilesystems($sourceUri, $destinationFileSystem, $targetFilePath, $config);
286
287
            return (string)Uri::create($destinationFileSystem, $targetFilePath);
288
        } catch (FilesystemException $e) {
289
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
290
        }
291
    }
292
293
    /**
294
     * @inheritDoc
295
     */
296
    public function copy(
297
        string $sourceUri,
298
        string $destinationFileSystem,
299
        ?string $targetFilePath = null,
300
        array $config = []
301
    ): string {
302
        /** @var FilesystemOperator $sourceFilesystem */
303
        [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($sourceUri);
304
305
        $destinationFilesystem = $this->getFileSystem($destinationFileSystem);
306
307
        try {
308
            $targetFilePath = $targetFilePath ?: $sourcePath;
309
310
            $sourceFilesystem === $destinationFilesystem
311
                ? $this->copyInSameFilesystem($sourceFilesystem, $sourcePath, $targetFilePath, $config)
312
                : $this->copyAcrossFilesystem(
313
                    $config['visibility'] ?? null,
314
                    $sourceFilesystem,
315
                    $sourcePath,
316
                    $destinationFilesystem,
317
                    $targetFilePath,
318
                    $config
319
                );
320
321
            return (string)Uri::create($destinationFileSystem, $targetFilePath);
322
        } catch (FilesystemException $e) {
323
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
324
        }
325
    }
326
327
    /**
328
     * @inheritDoc
329
     */
330
    public function delete(string $uri): void
331
    {
332
        /** @var FilesystemOperator $filesystem */
333
        [$filesystem, $path] = $this->determineFilesystemAndPath($uri);
334
335
        try {
336
            $filesystem->delete($path);
337
        } catch (FilesystemException $e) {
338
            throw new FileOperationException($e->getMessage(), (int)$e->getCode(), $e);
339
        }
340
    }
341
342
    /**
343
     * Mount new filesystem in collection
344
     * Key should be unique
345
     *
346
     * @param string $key
347
     * @param FilesystemOperator $filesystem
348
     *
349
     * @throws MountException
350
     */
351
    protected function mountFilesystem(string $key, FilesystemOperator $filesystem): void
352
    {
353
        if ($this->isFileSystemExists($key)) {
354
            throw new MountException(\sprintf('Filesystem %s is already mounted', $key));
355
        }
356
357
        $this->fileSystems[$key] = $filesystem;
358
    }
359
360
    /**
361
     * Check if filesystem was mounted
362
     *
363
     * @param string $key
364
     *
365
     * @return bool
366
     */
367
    protected function isFileSystemExists(string $key): bool
368
    {
369
        return \array_key_exists($key, $this->fileSystems);
370
    }
371
372
    /**
373
     * Identify used filesystem key and filepath by provided uri
374
     *
375
     * @param string $uri
376
     *
377
     * @return array { 0: FilesystemOperator, 1: string }
378
     *
379
     * @throws MountException
380
     * @throws UriException
381
     */
382
    protected function determineFilesystemAndPath(string $uri): array
383
    {
384
        $uriStructure = $this->uriParser->parse($uri);
0 ignored issues
show
Bug introduced by
$uri of type string is incompatible with the type Spiral\Storage\Parser\UriLikeType expected by parameter $uri of Spiral\Storage\Parser\UriParserInterface::parse(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

384
        $uriStructure = $this->uriParser->parse(/** @scrutinizer ignore-type */ $uri);
Loading history...
385
386
        return [
387
            $this->getFileSystem($uriStructure->getFileSystem()),
388
            $uriStructure->getPath(),
389
        ];
390
    }
391
392
    /**
393
     * Copy file in one filesystem
394
     *
395
     * @param FilesystemOperator $sourceFilesystem
396
     * @param string $sourcePath
397
     * @param string $destinationPath
398
     * @param array $config
399
     *
400
     * @throws FilesystemException
401
     */
402
    protected function copyInSameFilesystem(
403
        FilesystemOperator $sourceFilesystem,
404
        string $sourcePath,
405
        string $destinationPath,
406
        array $config = []
407
    ): void {
408
        $sourceFilesystem->copy($sourcePath, $destinationPath, $config);
409
    }
410
411
    /**
412
     * Copy file across different filesystems
413
     *
414
     * @param string|null $visibility
415
     * @param FilesystemOperator $sourceFilesystem
416
     * @param string $sourcePath
417
     * @param FilesystemOperator $destinationFilesystem
418
     * @param string $destinationPath
419
     * @param array $config
420
     *
421
     * @throws FilesystemException
422
     */
423
    protected function copyAcrossFilesystem(
424
        ?string $visibility,
425
        FilesystemOperator $sourceFilesystem,
426
        string $sourcePath,
427
        FilesystemOperator $destinationFilesystem,
428
        string $destinationPath,
429
        array $config = []
430
    ): void {
431
        $visibility = $visibility ?? $sourceFilesystem->visibility($sourcePath);
432
        $stream = $sourceFilesystem->readStream($sourcePath);
433
        $destinationFilesystem->writeStream(
434
            $destinationPath,
435
            $stream,
436
            !empty($config)
437
                ? array_merge($config, compact('visibility'))
438
                : compact('visibility')
439
        );
440
    }
441
442
    /**
443
     * Move file in one filesystem
444
     *
445
     * @param FilesystemOperator $sourceFilesystem
446
     * @param string $sourcePath
447
     * @param string $destinationPath
448
     * @param array $config
449
     *
450
     * @throws FilesystemException
451
     */
452
    protected function moveInTheSameFilesystem(
453
        FilesystemOperator $sourceFilesystem,
454
        string $sourcePath,
455
        string $destinationPath,
456
        array $config = []
457
    ): void {
458
        if ($sourcePath === $destinationPath && empty($config)) {
459
            return;
460
        }
461
462
        $sourceFilesystem->move($sourcePath, $destinationPath, $config);
463
    }
464
465
    /**
466
     * Move file across different filesystems
467
     *
468
     * @param string $sourceUri
469
     * @param string $destinationFileSystem
470
     * @param string|null $targetFilePath
471
     * @param array $config
472
     *
473
     * @throws StorageException
474
     */
475
    protected function moveAcrossFilesystems(
476
        string $sourceUri,
477
        string $destinationFileSystem,
478
        ?string $targetFilePath = null,
479
        array $config = []
480
    ): void {
481
        $this->copy($sourceUri, $destinationFileSystem, $targetFilePath, $config);
482
        $this->delete($sourceUri);
483
    }
484
}
485