Completed
Branch ci (2c70be)
by Florian
02:33
created

File   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 556
Duplicated Lines 0 %

Test Coverage

Coverage 57.93%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 128
c 2
b 0
f 0
dl 0
loc 556
ccs 84
cts 145
cp 0.5793
rs 8.64
wmc 47

36 Methods

Rating   Name   Duplication   Size   Complexity  
A filename() 0 3 1
A buildPath() 0 6 1
A variants() 0 3 1
A uuid() 0 3 1
A withMetadataKey() 0 6 1
A metadata() 0 3 1
A variant() 0 10 2
A hasVariant() 0 3 1
A path() 0 9 2
A create() 0 36 4
A withFilename() 0 9 2
A resource() 0 3 1
A withFile() 0 5 1
A withResource() 0 8 1
A withVariant() 0 6 1
A jsonSerialize() 0 3 1
A modelId() 0 3 1
A addToCollection() 0 5 1
A belongsToModel() 0 6 1
A withUuid() 0 6 1
A readableSize() 0 6 1
A withVariants() 0 9 2
A toArray() 0 15 1
A withoutMetadata() 0 6 1
A collection() 0 3 1
A assertStreamResource() 0 7 3
A hasVariants() 0 3 1
A mimeType() 0 3 1
A storage() 0 3 1
A variantPaths() 0 10 3
A extension() 0 3 1
A withMetadata() 0 6 1
A model() 0 3 1
A filesize() 0 3 1
A withoutMetadataKey() 0 6 1
A withPath() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like File often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use File, and based on these observations, apply Extract Interface, too.

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 RuntimeException;
22
use InvalidArgumentException;
23
24
/**
25
 * File
26
 */
27
class File implements FileInterface
28
{
29
    /**
30
     * @var int
31
     */
32
    protected int $id;
33
34
    /**
35
     * @var string
36
     */
37
    protected string $uuid;
38
39
    /**
40
     * @var string
41
     */
42
    protected string $filename;
43
44
    /**
45
     * @var int
46
     */
47
    protected int $filesize;
48
49
    /**
50
     * @var string
51
     */
52
    protected string $mimeType = '';
53
54
    /**
55
     * @var string|null
56
     */
57
    protected ?string $extension = null;
58
59
    /**
60
     * @var string
61
     */
62
    protected ?string $path = null;
63
64
    /**
65
     * @var string|null
66
     */
67
    protected ?string $collection = null;
68
69
    /**
70
     * @var string
71
     */
72
    protected string $storage = 'local';
73
74
    /**
75
     * @var array
76
     */
77
    protected array $metadata = [];
78
79
    /**
80
     * @var string|null
81
     */
82
    protected ?string $model = null;
83
84
    /**
85
     * @var string|null
86
     */
87
    protected ?string $modelId = null;
88
89
    /**
90
     * Source file to be stored in our system
91
     *
92
     * @var mixed
93
     */
94
    protected $sourceFile;
95
96
    /**
97
     * @var resource
98
     */
99
    protected $resource;
100
101
    /**
102
     * @var array
103
     */
104
    protected array $variants = [];
105
106
    /**
107
     * Creates a new instance
108
     *
109
     * @param string $filename Filename
110
     * @param int $filesize Filesize
111
     * @param string $mimeType Mime Type
112
     * @param string $storage Storage config name
113
     * @param string|null $collection Collection name
114
     * @param string|null $model Model name
115
     * @param string|null $modelId Model id
116
     * @param array $variants Variants
117
     * @param array $metadata Meta data
118
     * @param resource|null $resource
119
     * @return self
120
     */
121 7
    public static function create(
122
        string $filename,
123
        int $filesize,
124
        string $mimeType,
125
        string $storage,
126
        ?string $collection = null,
127
        ?string $model = null,
128
        ?string $modelId = null,
129
        array $metadata = [],
130
        array $variants = [],
131
        $resource = null
132
    ): self {
133 7
        $that = new self();
134
135 7
        $that->filename = $filename;
136 7
        $that->filesize = $filesize;
137 7
        $that->mimeType = $mimeType;
138 7
        $that->storage = $storage;
139 7
        $that->model = $model;
140 7
        $that->modelId = $modelId;
141 7
        $that->collection = $collection;
142 7
        $that->variants = $variants;
143 7
        $that->metadata = $metadata;
144
145 7
        if (is_resource($resource)) {
146
            $that = $that->withResource($resource);
147 7
        } elseif ($resource !== null) {
0 ignored issues
show
introduced by
The condition $resource !== null is always false.
Loading history...
148
            throw new InvalidArgumentException(sprintf(
149
               'The $resource argument is not a valid resource'
150
            ));
151
        }
152
153 7
        $extension = pathinfo($filename, PATHINFO_EXTENSION);
154 7
        $that->extension = empty($extension) ? null : (string)$extension;
155
156 7
        return $that;
157
    }
158
159
    /**
160
     * Storage name
161
     *
162
     * @return string
163
     */
164 1
    public function storage(): string
165
    {
166 1
        return $this->storage;
167
    }
168
169
    /**
170
     * UUID of the file
171
     *
172
     * @param string $uuid UUID string
173
     * @return self
174
     */
175 5
    public function withUuid(string $uuid): self
176
    {
177 5
        $that = clone $this;
178 5
        $that->uuid = $uuid;
179
180 5
        return $that;
181
    }
182
183
    /**
184
     * Stream resource that should be stored
185
     *
186
     * @return resource|null
187
     */
188 1
    public function resource()
189
    {
190 1
        return $this->resource;
191
    }
192
193
    /**
194
     * Same as withResource() but takes a file path
195
     *
196
     * @param string $file File
197
     * @return self
198
     */
199
    public function withFile(string $file): self
200
    {
201
        $resource = fopen($file, 'rb');
202
203
        return $this->withResource($resource);
204
    }
205
206
    /**
207
     * @param mixed $resource
208
     */
209 6
    protected function assertStreamResource($resource): void
210
    {
211
        if (
212 6
            !is_resource($resource)
213 6
            || get_resource_type($resource) !== 'stream'
214
        ) {
215
            throw InvalidStreamResourceException::create();
216
        }
217 6
    }
218
219
    /**
220
     * Stream resource of the file to be stored
221
     *
222
     * @param resource  $resource
223
     * @return self
224
     */
225 6
    public function withResource($resource): self
226
    {
227 6
        $this->assertStreamResource($resource);
228
229 6
        $that = clone $this;
230 6
        $that->resource = $resource;
231
232 6
        return $that;
233
    }
234
235
    /**
236
     * Assign a model and model id to a file
237
     *
238
     * @param string $model Model
239
     * @param string|int $modelId Model ID, UUID string or integer
240
     * @return $this
241
     */
242 5
    public function belongsToModel(string $model, $modelId): self
243
    {
244 5
        $this->model = $model;
245 5
        $this->modelId = $modelId;
246
247 5
        return $this;
248
    }
249
250
    /**
251
     * Adds the file to a collection
252
     *
253
     * @param string $collection Collection
254
     * @return $this
255
     */
256 3
    public function addToCollection(string $collection): self
257
    {
258 3
        $this->collection = $collection;
259
260 3
        return $this;
261
    }
262
263
    /**
264
     * Sets the path, immutable
265
     *
266
     * @param string $path Path to the file
267
     * @return $this
268
     */
269
    public function withPath(string $path): self
270
    {
271
        $that = clone $this;
272
        $that->path = $path;
273
274
        return $that;
275
    }
276
277
    /**
278
     * Filename
279
     *
280
     * @param string $filename Filename
281
     * @return self
282
     */
283 2
    public function withFilename(string $filename): self
284
    {
285 2
        $that = clone $this;
286 2
        $that->filename = $filename;
287
288 2
        $extension = pathinfo($filename, PATHINFO_EXTENSION);
289 2
        $that->extension = empty($extension) ? null : (string)$extension;
290
291 2
        return $that;
292
    }
293
294
    /**
295
     * The collections name this file belongs into
296
     *
297
     * @return string|null
298
     */
299 4
    public function collection(): ?string
300
    {
301 4
        return $this->collection;
302
    }
303
304
    /**
305
     * Model name
306
     *
307
     * @return string|null
308
     */
309 5
    public function model(): ?string
310
    {
311 5
        return $this->model;
312
    }
313
314
    /**
315
     * Model ID
316
     *
317
     * @return string|null
318
     */
319 4
    public function modelId(): ?string
320
    {
321 4
        return $this->modelId;
322
    }
323
324
    /**
325
     * Size of the file in bytes
326
     *
327
     * @return int
328
     */
329
    public function filesize(): int
330
    {
331
        return $this->filesize;
332
    }
333
334
    /**
335
     * Returns a human readable file size
336
     *
337
     * @return string
338
     */
339
    public function readableSize(): string
340
    {
341
        $i = floor(log($this->filesize, 1024));
342
        $round = (string)round($this->filesize / (1024 ** $i), [0, 0, 2, 2, 3][$i]);
343
344
        return $round . ['B','kB','MB','GB','TB'][$i];
345
    }
346
347
    /**
348
     * @return string|null
349
     */
350 4
    public function extension(): ?string
351
    {
352 4
        return $this->extension;
353
    }
354
355
    /**
356
     * @return string
357
     */
358 4
    public function mimeType(): string
359
    {
360 4
        return $this->mimeType;
361
    }
362
363
    /**
364
     * @return string
365
     */
366 4
    public function filename(): string
367
    {
368 4
        return $this->filename;
369
    }
370
371
    /**
372
     * @return string
373
     */
374 4
    public function uuid(): string
375
    {
376 4
        return $this->uuid;
377
    }
378
379
    /**
380
     * @return string
381
     */
382 3
    public function path(): string
383
    {
384 3
        if ($this->path === null) {
385 1
            throw new RuntimeException(
386 1
                'Path has not been set'
387
            );
388
        }
389
390 2
        return $this->path;
391
    }
392
393
    /**
394
     * Builds the path for this file
395
     *
396
     * @param \Phauthentic\Infrastructure\Storage\PathBuilder\PathBuilderInterface $pathBuilder Path Builder
397
     * @return $this
398
     */
399 2
    public function buildPath(PathBuilderInterface $pathBuilder): self
400
    {
401 2
        $that = clone $this;
402 2
        $that->path = $pathBuilder->path($this);
403
404 2
        return $that;
405
    }
406
407
    /**
408
     * @param array $metadata Meta data
409
     * @return $this
410
     */
411 1
    public function withMetadata(array $metadata): self
412
    {
413 1
        $that = clone $this;
414 1
        $that->metadata = $metadata;
415
416 1
        return $that;
417
    }
418
419
    /**
420
     * @param string $name Name
421
     * @param mixed $data Data
422
     * @return $this
423
     */
424 2
    public function withMetadataKey(string $name, $data): self
425
    {
426 2
        $that = clone $this;
427 2
        $that->metadata[$name] = $data;
428
429 2
        return $that;
430
    }
431
432
    /**
433
     * @param string $name Name
434
     * @return $this
435
     */
436
    public function withoutMetadataKey(string $name): self
437
    {
438
        $that = clone $this;
439
        unset($that->metadata[$name]);
440
441
        return $that;
442
    }
443
444
    /**
445
     * @return $this
446
     */
447
    public function withoutMetadata(): self
448
    {
449
        $that = clone $this;
450
        $that->metadata = [];
451
452
        return $that;
453
    }
454
455
    /**
456
     * @return array
457
     */
458
    public function metadata(): array
459
    {
460
        return $this->metadata;
461
    }
462
463
    /**
464
     * @return bool
465
     */
466
    public function hasVariants(): bool
467
    {
468
        return !empty($this->variants);
469
    }
470
471
    /**
472
     * @param string $name Name
473
     * @return bool
474
     */
475
    public function hasVariant(string $name): bool
476
    {
477
        return isset($this->variants[$name]);
478
    }
479
480
    /**
481
     * @return array
482
     */
483 2
    public function variants(): array
484
    {
485 2
        return $this->variants;
486
    }
487
488
    /**
489
     * Returns a variant by name
490
     *
491
     * @param string $name Name
492
     * @return array
493
     */
494
    public function variant(string $name): array
495
    {
496
        if (!isset($this->variants[$name])) {
497
            throw new RuntimeException(
498
                'Variant %s does not exist',
499
                $name
0 ignored issues
show
Bug introduced by
$name of type string is incompatible with the type integer expected by parameter $code of RuntimeException::__construct(). ( Ignorable by Annotation )

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

499
                /** @scrutinizer ignore-type */ $name
Loading history...
500
            );
501
        }
502
503
        return $this->variants[$name];
504
    }
505
506
    /**
507
     * Adds a variant
508
     *
509
     * @param string $name Name
510
     * @param array $data Data
511
     * @return $this
512
     */
513
    public function withVariant(string $name, array $data): self
514
    {
515
        $that = clone $this;
516
        $that->variants[$name] = $data;
517
518
        return $that;
519
    }
520
521
    /**
522
     * Gets the paths for all variants
523
     *
524
     * @return array
525
     */
526
    public function variantPaths(): array
527
    {
528
        $paths = [];
529
        foreach ($this->variants as $variant => $data) {
530
            if (isset($data['path'])) {
531
                $paths[$variant] = $data['path'];
532
            }
533
        }
534
535
        return $paths;
536
    }
537
538
    /**
539
     * Sets many variants at once
540
     *
541
     * @param array $variants Variants
542
     * @param bool $merge Merge Variants, default is true
543
     * @return $this
544
     */
545 2
    public function withVariants(array $variants, bool $merge = true): self
546
    {
547 2
        $that = clone $this;
548 2
        $that->variants = array_merge_recursive(
549 2
            $merge ? $that->variants : [],
550
            $variants
551
        );
552
553 2
        return $that;
554
    }
555
556
    /**
557
     * @return array
558
     */
559
    public function toArray(): array
560
    {
561
        return [
562
            'uuid' => $this->uuid,
563
            'filename' => $this->filename,
564
            'filesize' => $this->filesize,
565
            'mimeType' => $this->mimeType,
566
            'extension' => $this->extension,
567
            'path' => $this->path,
568
            'model' => $this->model,
569
            'modelId' => $this->modelId,
570
            'collection' => $this->collection,
571
            'readableSize' => $this->readableSize(),
572
            'variants' => $this->variants,
573
            'metaData' => $this->metadata,
574
        ];
575
    }
576
577
    /**
578
     * @inheritDoc
579
     */
580
    public function jsonSerialize()
581
    {
582
        return $this->toArray();
583
    }
584
}
585