File::variantPaths()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 10
rs 10
1
<?php
2
3
/**
4
 * Copyright (c) Florian Krämer (https://florian-kraemer.net)
5
 * Licensed under The MIT License
6
 * For full copyright and license information, please see the LICENSE.txt
7
 * Redistributions of files must retain the above copyright notice.
8
 *
9
 * @copyright Copyright (c) Florian Krämer (https://florian-kraemer.net)
10
 * @author    Florian Krämer
11
 * @link      https://github.com/Phauthentic
12
 * @license   https://opensource.org/licenses/MIT MIT License
13
 */
14
15
declare(strict_types=1);
16
17
namespace Phauthentic\Infrastructure\Storage;
18
19
use Phauthentic\Infrastructure\Storage\Exception\InvalidStreamResourceException;
20
use Phauthentic\Infrastructure\Storage\PathBuilder\PathBuilderInterface;
21
use Phauthentic\Infrastructure\Storage\Processor\Exception\VariantDoesNotExistException;
22
use Phauthentic\Infrastructure\Storage\UrlBuilder\UrlBuilderInterface;
23
use RuntimeException;
24
25
/**
26
 * File
27
 */
28
class File implements FileInterface
29
{
30
    /**
31
     * @var int
32
     */
33
    protected int $id;
34
35
    /**
36
     * @var string
37
     */
38
    protected string $uuid;
39
40
    /**
41
     * @var string
42
     */
43
    protected string $filename;
44
45
    /**
46
     * @var int
47
     */
48
    protected int $filesize;
49
50
    /**
51
     * @var string
52
     */
53
    protected string $mimeType = '';
54
55
    /**
56
     * @var string|null
57
     */
58
    protected ?string $extension = null;
59
60
    /**
61
     * @var string|null
62
     */
63
    protected ?string $path = null;
64
65
    /**
66
     * @var string|null
67
     */
68
    protected ?string $collection = null;
69
70
    /**
71
     * @var string
72
     */
73
    protected string $storage = 'local';
74
75
    /**
76
     * @var array<string, mixed>
77
     */
78
    protected array $metadata = [];
79
80
    /**
81
     * @var string|null
82
     */
83
    protected ?string $model = null;
84
85
    /**
86
     * @var string|null
87
     */
88
    protected ?string $modelId = null;
89
90
    /**
91
     * Source file to be stored in our system
92
     *
93
     * @var mixed
94
     */
95
    protected $sourceFile;
96
97
    /**
98
     * @var resource
99
     */
100
    protected $resource;
101
102
    /**
103
     * @var string
104
     */
105
    protected string $url = '';
106
107
    /**
108
     * @var array<string, array<string, mixed>>
109
     */
110
    protected array $variants = [];
111
112
    /**
113
     * Creates a new instance
114
     *
115
     * @param string $filename Filename
116
     * @param int $filesize Filesize
117
     * @param string $mimeType Mime Type
118
     * @param string $storage Storage config name
119
     * @param string|null $collection Collection name
120
     * @param string|null $model Model name
121
     * @param string|null $modelId Model id
122
     * @param array<string, mixed> $variants Variants
123
     * @param array<string, mixed> $metadata Meta data
124
     * @param resource|null $resource
125
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
126
     */
127
    public static function create(
128
        string $filename,
129
        int $filesize,
130
        string $mimeType,
131
        string $storage,
132
        ?string $collection = null,
133
        ?string $model = null,
134
        ?string $modelId = null,
135
        array $metadata = [],
136
        array $variants = [],
137
        $resource = null
138
    ): FileInterface {
139
        $that = new self();
140
141
        $that->filename = $filename;
142
        $that->filesize = $filesize;
143
        $that->mimeType = $mimeType;
144
        $that->storage = $storage;
145
        $that->model = $model;
146
        $that->modelId = $modelId;
147
        $that->collection = $collection;
148
        $that->variants = $variants;
149
        $that->metadata = $metadata;
150
151
        $extension = pathinfo($filename, PATHINFO_EXTENSION);
152
        $that->extension = empty($extension) ? null : (string)$extension;
153
154
        if ($resource !== null) {
155
            $that = $that->withResource($resource);
156
        }
157
158
        return $that;
159
    }
160
161
    /**
162
     * Storage name
163
     *
164
     * @return string
165
     */
166
    public function storage(): string
167
    {
168
        return $this->storage;
169
    }
170
171
    /**
172
     * UUID of the file
173
     *
174
     * @param string $uuid UUID string
175
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
176
     */
177
    public function withUuid(string $uuid): FileInterface
178
    {
179
        $that = clone $this;
180
        $that->uuid = $uuid;
181
182
        return $that;
183
    }
184
185
    /**
186
     * Stream resource that should be stored
187
     *
188
     * @return resource|null
189
     */
190
    public function resource()
191
    {
192
        return $this->resource;
193
    }
194
195
    /**
196
     * Same as withResource() but takes a file path
197
     *
198
     * @param string $file File
199
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
200
     */
201
    public function withFile(string $file): FileInterface
202
    {
203
        $resource = fopen($file, 'rb');
204
205
        return $this->withResource($resource);
206
    }
207
208
    /**
209
     * @param mixed $resource
210
     * @return void
211
     */
212
    protected function assertStreamResource($resource): void
213
    {
214
        if (
215
            !is_resource($resource)
216
            || get_resource_type($resource) !== 'stream'
217
        ) {
218
            throw InvalidStreamResourceException::create();
219
        }
220
    }
221
222
    /**
223
     * Stream resource of the file to be stored
224
     *
225
     * @param resource $resource Stream Resource
226
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
227
     */
228
    public function withResource($resource): FileInterface
229
    {
230
        $this->assertStreamResource($resource);
231
232
        $that = clone $this;
233
        $that->resource = $resource;
234
235
        return $that;
236
    }
237
238
    /**
239
     * Assign a model and model id to a file
240
     *
241
     * @param string $model Model
242
     * @param string|int $modelId Model ID, UUID string or integer
243
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
244
     */
245
    public function belongsToModel(string $model, $modelId): FileInterface
246
    {
247
        $this->model = $model;
248
        $this->modelId = (string)$modelId;
249
250
        return $this;
251
    }
252
253
    /**
254
     * Adds the file to a collection
255
     *
256
     * @param string $collection Collection
257
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
258
     */
259
    public function addToCollection(string $collection): FileInterface
260
    {
261
        $this->collection = $collection;
262
263
        return $this;
264
    }
265
266
    /**
267
     * Sets the path, immutable
268
     *
269
     * @param string $path Path to the file
270
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
271
     */
272
    public function withPath(string $path): FileInterface
273
    {
274
        $that = clone $this;
275
        $that->path = $path;
276
277
        return $that;
278
    }
279
280
    /**
281
     * Filename
282
     *
283
     * @param string $filename Filename
284
     * @return self
285
     */
286
    public function withFilename(string $filename): self
287
    {
288
        $that = clone $this;
289
        $that->filename = $filename;
290
291
        $extension = pathinfo($filename, PATHINFO_EXTENSION);
292
        $that->extension = empty($extension) ? null : (string)$extension;
293
294
        return $that;
295
    }
296
297
    /**
298
     * The collections name this file belongs into
299
     *
300
     * @return string|null
301
     */
302
    public function collection(): ?string
303
    {
304
        return $this->collection;
305
    }
306
307
    /**
308
     * Model name
309
     *
310
     * @return string|null
311
     */
312
    public function model(): ?string
313
    {
314
        return $this->model;
315
    }
316
317
    /**
318
     * Model ID
319
     *
320
     * @return string|null
321
     */
322
    public function modelId(): ?string
323
    {
324
        return $this->modelId;
325
    }
326
327
    /**
328
     * Size of the file in bytes
329
     *
330
     * @return int
331
     */
332
    public function filesize(): int
333
    {
334
        return $this->filesize;
335
    }
336
337
    /**
338
     * Returns a human readable file size
339
     *
340
     * @return string
341
     */
342
    public function readableSize(): string
343
    {
344
        $i = (int)floor(log($this->filesize, 1024));
345
        $rounds = [0, 0, 2, 2, 3];
346
        $units = ['B','kB','MB','GB','TB'];
347
348
        if ($i < 0 || $i >= count($rounds)) {
349
            $i = 0;
350
        }
351
352
        $round = (string)round($this->filesize / (1024 ** $i), $rounds[$i]);
353
354
        return $round . $units[$i];
355
    }
356
357
    /**
358
     * @return string|null
359
     */
360
    public function extension(): ?string
361
    {
362
        return $this->extension;
363
    }
364
365
    /**
366
     * @return string
367
     */
368
    public function mimeType(): string
369
    {
370
        return $this->mimeType;
371
    }
372
373
    /**
374
     * @return string
375
     */
376
    public function filename(): string
377
    {
378
        return $this->filename;
379
    }
380
381
    /**
382
     * @return string
383
     */
384
    public function uuid(): string
385
    {
386
        return $this->uuid;
387
    }
388
389
    /**
390
     * @return string
391
     */
392
    public function path(): string
393
    {
394
        if ($this->path === null) {
395
            throw new RuntimeException(
396
                'Path has not been set'
397
            );
398
        }
399
400
        return $this->path;
401
    }
402
403
    /**
404
     * Builds the path for this file
405
     *
406
     * @param \Phauthentic\Infrastructure\Storage\PathBuilder\PathBuilderInterface $pathBuilder Path Builder
407
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
408
     */
409
    public function buildPath(PathBuilderInterface $pathBuilder): FileInterface
410
    {
411
        $that = clone $this;
412
        $that->path = $pathBuilder->path($this);
413
414
        return $that;
415
    }
416
417
    /**
418
     * @param array<string, mixed> $metadata Meta data
419
     * @param bool $overwrite Overwrite whole metadata instead of assoc merging.
420
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
421
     */
422
    public function withMetadata(array $metadata, bool $overwrite = false): FileInterface
423
    {
424
        $that = clone $this;
425
        if ($overwrite) {
426
            $that->metadata = $metadata;
427
        } else {
428
            $that->metadata = $metadata + $that->metadata;
429
        }
430
431
        return $that;
432
    }
433
434
    /**
435
     * @inheritDoc
436
     */
437
    public function withMetadataByKey(string $name, $data): FileInterface
438
    {
439
        $that = clone $this;
440
        $that->metadata[$name] = $data;
441
442
        return $that;
443
    }
444
445
    /**
446
     * @inheritDoc
447
     */
448
    public function withoutMetadataByKey(string $name): FileInterface
449
    {
450
        $that = clone $this;
451
        unset($that->metadata[$name]);
452
453
        return $that;
454
    }
455
456
    /**
457
     * @inheritDoc
458
     */
459
    public function withoutMetadata(): self
460
    {
461
        $that = clone $this;
462
        $that->metadata = [];
463
464
        return $that;
465
    }
466
467
    /**
468
     * @inheritDoc
469
     */
470
    public function metadata(): array
471
    {
472
        return $this->metadata;
473
    }
474
475
    /**
476
     * @return bool
477
     */
478
    public function hasVariants(): bool
479
    {
480
        return !empty($this->variants);
481
    }
482
483
    /**
484
     * @param string $name Name
485
     * @return bool
486
     */
487
    public function hasVariant(string $name): bool
488
    {
489
        return isset($this->variants[$name]);
490
    }
491
492
    /**
493
     * @return array<string, array<string, mixed>>
494
     */
495
    public function variants(): array
496
    {
497
        return $this->variants;
498
    }
499
500
    /**
501
     * Returns a variant by name
502
     *
503
     * @param string $name Name
504
     * @return array<string, mixed>
505
     */
506
    public function variant(string $name): array
507
    {
508
        if (!isset($this->variants[$name])) {
509
            throw VariantDoesNotExistException::withName($name);
510
        }
511
512
        return $this->variants[$name];
513
    }
514
515
    /**
516
     * Adds a variant
517
     *
518
     * @param string $name Name
519
     * @param array<string, mixed> $data Data
520
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
521
     */
522
    public function withVariant(string $name, array $data): FileInterface
523
    {
524
        $that = clone $this;
525
        $that->variants[$name] = $data;
526
527
        return $that;
528
    }
529
530
    /**
531
     * Gets the paths for all variants
532
     *
533
     * @return array<string, string>
534
     */
535
    public function variantPaths(): array
536
    {
537
        $paths = [];
538
        foreach ($this->variants as $variant => $data) {
539
            if (!empty($data['path'])) {
540
                $paths[$variant] = $data['path'];
541
            }
542
        }
543
544
        return $paths;
545
    }
546
547
    /**
548
     * @param string $key
549
     * @param mixed $data;
550
     */
551
    public function withMetadataKey(string $key, $data): FileInterface
552
    {
553
        $that = clone $this;
554
        $that->metadata[$key] = $data;
555
556
        return $that;
557
    }
558
559
    /**
560
     * @inheritDoc
561
     */
562
    public function withoutMetadataKey(string $key): FileInterface
563
    {
564
        $that = clone $this;
565
        unset($that->metadata[$key]);
566
567
        return $that;
568
    }
569
570
    /**
571
     * Sets many variants at once
572
     *
573
     * @param array<string, array<string, mixed>> $variants Variants
574
     * @param bool $merge Merge Variants, default is true
575
     * @return \Phauthentic\Infrastructure\Storage\FileInterface
576
     */
577
    public function withVariants(array $variants, bool $merge = true): FileInterface
578
    {
579
        $that = clone $this;
580
        $that->variants = array_merge_recursive(
581
            $merge ? $that->variants : [],
582
            $variants
583
        );
584
585
        return $that;
586
    }
587
588
    /**
589
     * @inheritDoc
590
     */
591
    public function buildUrl(UrlBuilderInterface $urlBuilder): FileInterface
592
    {
593
        $this->url = $urlBuilder->url($this);
594
595
        return $this;
596
    }
597
598
    /**
599
     * @inheritDoc
600
     */
601
    public function url(): string
602
    {
603
        return $this->url;
604
    }
605
606
    /**
607
     * @inheritDoc
608
     */
609
    public function withUrl(string $url): FileInterface
610
    {
611
        $that = clone $this;
612
        $that->url = $url;
613
614
        return $that;
615
    }
616
617
    /**
618
     * @return array<string, mixed>
619
     */
620
    public function toArray(): array
621
    {
622
        return [
623
            'uuid' => $this->uuid,
624
            'filename' => $this->filename,
625
            'filesize' => $this->filesize,
626
            'mimeType' => $this->mimeType,
627
            'extension' => $this->extension,
628
            'path' => $this->path,
629
            'model' => $this->model,
630
            'modelId' => $this->modelId,
631
            'collection' => $this->collection,
632
            'readableSize' => $this->readableSize(),
633
            'variants' => $this->variants,
634
            'metadata' => $this->metadata,
635
            'url' => $this->url
636
        ];
637
    }
638
639
    /**
640
     * @inheritDoc
641
     * @return array<string, mixed>
642
     */
643
    public function jsonSerialize(): array
644
    {
645
        return $this->toArray();
646
    }
647
}
648