Completed
Push — master ( cf2248...7c366b )
by Arman
15s queued 11s
created

UploadedFile   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 418
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 110
dl 0
loc 418
rs 8.72
c 2
b 0
f 0
wmc 46

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A allowed() 0 7 2
A getMd5() 0 3 1
A applyModifications() 0 6 1
A moveUploadedFile() 0 9 3
A getNameWithExtension() 0 3 1
A getErrorCode() 0 3 1
A getErrorMessage() 0 3 1
A setName() 0 4 1
A isUploaded() 0 3 1
A getName() 0 7 2
A setRemoteFileSystem() 0 4 1
B save() 0 39 11
A getExtension() 0 7 2
A getMimeType() 0 11 2
A loadAllowedMimeTypesFromConfig() 0 14 5
A modify() 0 14 3
A isImage() 0 3 1
A getRemoteFileSystem() 0 3 1
A setAllowedMimeTypes() 0 4 1
A getDimensions() 0 11 2
A setAllowedMimeTypesMap() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like UploadedFile 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 UploadedFile, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 2.9.8
13
 */
14
15
namespace Quantum\Libraries\Storage;
16
17
use Quantum\Libraries\Storage\Contracts\FilesystemAdapterInterface;
18
use Quantum\Libraries\Storage\Exceptions\FileSystemException;
19
use Quantum\Libraries\Storage\Exceptions\FileUploadException;
20
use Quantum\Libraries\Storage\Factories\FileSystemFactory;
21
use Quantum\Libraries\Lang\Exceptions\LangException;
22
use Quantum\Environment\Exceptions\EnvException;
23
use Quantum\Config\Exceptions\ConfigException;
24
use Quantum\App\Exceptions\BaseException;
25
use Quantum\Di\Exceptions\DiException;
26
use Quantum\Loader\Setup;
27
use Gumlet\ImageResizeException;
28
use ReflectionException;
29
use Gumlet\ImageResize;
30
use SplFileInfo;
31
use finfo;
32
33
/**
34
 * Class File
35
 * @package Quantum\Libraries\Storage
36
 */
37
class UploadedFile extends SplFileInfo
38
{
39
40
    /**
41
     * Local File System
42
     * @var FilesystemAdapterInterface
43
     */
44
    protected $localFileSystem;
45
46
    /**
47
     * Remove File System
48
     * @var FilesystemAdapterInterface
49
     */
50
    protected $remoteFileSystem = null;
51
52
    /**
53
     * Original file name provided by client
54
     * @var string
55
     */
56
    protected $originalName = null;
57
58
    /**
59
     * File name (without extension)
60
     * @var string
61
     */
62
    protected $name = null;
63
64
    /**
65
     * File extension
66
     * @var string
67
     */
68
    protected $extension = null;
69
70
    /**
71
     * File mime type
72
     * @var string
73
     */
74
    protected $mimetype = null;
75
76
    /**
77
     * ImageResize function name
78
     * @var string
79
     */
80
    protected $imageModifierFuncName = null;
81
82
    /**
83
     * ImageResize function arguments
84
     * @var array
85
     */
86
    protected $params = [];
87
88
    /**
89
     * Upload error code messages
90
     * @var array
91
     */
92
    protected $errorMessages = [
93
        1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
94
        2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
95
        3 => 'The uploaded file was only partially uploaded',
96
        4 => 'No file was uploaded',
97
        6 => 'Missing a temporary folder',
98
        7 => 'Failed to write file to disk',
99
        8 => 'A PHP extension stopped the file upload'
100
    ];
101
102
    /**
103
     * Allowed mime types => allowed extensions map
104
     * @var array
105
     */
106
    protected $allowedMimeTypes = [
107
        'image/jpeg' => ['jpg', 'jpeg'],
108
        'image/png' => ['png'],
109
        'application/pdf' => ['pdf'],
110
    ];
111
112
    /**
113
     * Upload error code
114
     * @var int
115
     */
116
    protected $errorCode;
117
118
    /**
119
     * @param array $meta
120
     * @throws BaseException
121
     * @throws ConfigException
122
     * @throws DiException
123
     * @throws ReflectionException
124
     */
125
    public function __construct(array $meta)
126
    {
127
        $this->localFileSystem = FileSystemFactory::get();
0 ignored issues
show
Documentation Bug introduced by
It seems like Quantum\Libraries\Storag...ileSystemFactory::get() of type Quantum\Libraries\Storage\FileSystem is incompatible with the declared type Quantum\Libraries\Storag...esystemAdapterInterface of property $localFileSystem.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
128
129
        $this->originalName = $meta['name'];
130
        $this->errorCode = $meta['error'];
131
132
        $this->loadAllowedMimeTypesFromConfig();
133
134
        parent::__construct($meta['tmp_name']);
135
    }
136
137
    /**
138
     * Sets the allowed mime types => extensions map
139
     * @param array $allowedMimeTypes
140
     * @param bool $merge
141
     * @return $this
142
     */
143
    public function setAllowedMimeTypes(array $allowedMimeTypes, bool $merge = true): UploadedFile
144
    {
145
        $this->setAllowedMimeTypesMap($allowedMimeTypes, $merge);
146
        return $this;
147
    }
148
149
    /**
150
     * Get name
151
     * @return string
152
     */
153
    public function getName(): string
154
    {
155
        if (!$this->name) {
156
            $this->name = $this->localFileSystem->fileName($this->originalName);
0 ignored issues
show
Bug introduced by
The method fileName() does not exist on Quantum\Libraries\Storag...esystemAdapterInterface. It seems like you code against a sub-type of Quantum\Libraries\Storag...esystemAdapterInterface such as Quantum\Libraries\Storag...\LocalFileSystemAdapter. ( Ignorable by Annotation )

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

156
            /** @scrutinizer ignore-call */ 
157
            $this->name = $this->localFileSystem->fileName($this->originalName);
Loading history...
157
        }
158
159
        return $this->name;
160
    }
161
162
    /**
163
     * Set name (without extension)
164
     * @param string $name
165
     * @return $this
166
     */
167
    public function setName(string $name): UploadedFile
168
    {
169
        $this->name = $name;
170
        return $this;
171
    }
172
173
    /**
174
     * Sets the remote file system adapter
175
     * @param FilesystemAdapterInterface $remoteFileSystem
176
     * @return $this
177
     */
178
    public function setRemoteFileSystem(FilesystemAdapterInterface $remoteFileSystem): UploadedFile
179
    {
180
        $this->remoteFileSystem = $remoteFileSystem;
181
        return $this;
182
    }
183
184
    /**
185
     * Gets the remote file system adapter
186
     * @return FilesystemAdapterInterface|null
187
     */
188
    public function getRemoteFileSystem(): ?FilesystemAdapterInterface
189
    {
190
        return $this->remoteFileSystem;
191
    }
192
193
    /**
194
     * Get file extension (without leading dot)
195
     * @return string
196
     */
197
    public function getExtension(): string
198
    {
199
        if (!$this->extension) {
200
            $this->extension = strtolower($this->localFileSystem->extension($this->originalName));
0 ignored issues
show
Bug introduced by
The method extension() does not exist on Quantum\Libraries\Storag...esystemAdapterInterface. It seems like you code against a sub-type of Quantum\Libraries\Storag...esystemAdapterInterface such as Quantum\Libraries\Storag...\LocalFileSystemAdapter. ( Ignorable by Annotation )

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

200
            $this->extension = strtolower($this->localFileSystem->/** @scrutinizer ignore-call */ extension($this->originalName));
Loading history...
201
        }
202
203
        return $this->extension;
204
    }
205
206
    /**
207
     * Get file name with extension
208
     * @return string
209
     */
210
    public function getNameWithExtension(): string
211
    {
212
        return $this->getName() . '.' . $this->getExtension();
213
    }
214
215
    /**
216
     * Get mime type
217
     * @return string
218
     */
219
    public function getMimeType(): string
220
    {
221
        if (!$this->mimetype) {
222
            $fileInfo = new finfo(FILEINFO_MIME);
223
            $mimetype = $fileInfo->file($this->getPathname());
224
            $mimetypeParts = preg_split('/\s*[;,]\s*/', $mimetype);
225
            $this->mimetype = strtolower($mimetypeParts[0]);
226
            unset($fileInfo);
227
        }
228
229
        return $this->mimetype;
230
    }
231
232
    /**
233
     * Get md5
234
     * @return string
235
     */
236
    public function getMd5(): string
237
    {
238
        return md5_file($this->getPathname());
239
    }
240
241
    /**
242
     * Get image dimensions
243
     * @return array
244
     * @throws FileUploadException
245
     */
246
    public function getDimensions(): array
247
    {
248
        if (!$this->isImage($this->getPathname())) {
249
            throw FileUploadException::fileTypeNotAllowed($this->getExtension());
250
        }
251
252
        list($width, $height) = getimagesize($this->getPathname());
253
254
        return [
255
            'width' => $width,
256
            'height' => $height
257
        ];
258
    }
259
260
    /**
261
     * Save the uploaded file
262
     * @param string $dest
263
     * @param bool $overwrite
264
     * @return bool
265
     * @throws BaseException
266
     * @throws FileSystemException
267
     * @throws FileUploadException
268
     * @throws ImageResizeException
269
     * @throws EnvException
270
     */
271
    public function save(string $dest, bool $overwrite = false): bool
272
    {
273
        if ($this->errorCode !== UPLOAD_ERR_OK) {
274
            throw new FileUploadException($this->getErrorMessage());
275
        }
276
277
        if (!$this->localFileSystem->isFile($this->getPathname())) {
278
            throw FileUploadException::fileNotFound($this->getPathname());
279
        }
280
281
        if (!$this->allowed($this->getExtension(), $this->getMimeType())) {
282
            throw FileUploadException::fileTypeNotAllowed($this->getExtension());
283
        }
284
285
        $filePath = $dest . DS . $this->getNameWithExtension();
286
287
        if (!$this->remoteFileSystem) {
288
            if (!$this->localFileSystem->isDirectory($dest)) {
289
                throw FileSystemException::directoryNotExists($dest);
290
            }
291
292
            if (!$this->localFileSystem->isWritable($dest)) {
0 ignored issues
show
Bug introduced by
The method isWritable() does not exist on Quantum\Libraries\Storag...esystemAdapterInterface. It seems like you code against a sub-type of Quantum\Libraries\Storag...esystemAdapterInterface such as Quantum\Libraries\Storag...\LocalFileSystemAdapter. ( Ignorable by Annotation )

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

292
            if (!$this->localFileSystem->/** @scrutinizer ignore-call */ isWritable($dest)) {
Loading history...
293
                throw FileSystemException::directoryNotWritable($dest);
294
            }
295
296
            if ($overwrite === false && $this->localFileSystem->exists($filePath)) {
297
                throw FileSystemException::fileAlreadyExists($filePath);
298
            }
299
        }
300
301
        if (!$this->moveUploadedFile($filePath)) {
302
            return false;
303
        }
304
305
        if ($this->imageModifierFuncName) {
306
            $this->applyModifications($filePath);
307
        }
308
309
        return true;
310
    }
311
312
    /**
313
     * Sets modification function on image
314
     * @param string $funcName
315
     * @param array $params
316
     * @return $this
317
     * @throws BaseException
318
     * @throws FileUploadException
319
     * @throws LangException
320
     */
321
    public function modify(string $funcName, array $params): UploadedFile
322
    {
323
        if (!$this->isImage($this->getPathname())) {
324
            throw FileUploadException::fileTypeNotAllowed($this->getExtension());
325
        }
326
327
        if (!method_exists(ImageResize::class, $funcName)) {
328
            throw BaseException::methodNotSupported($funcName, ImageResize::class);
329
        }
330
331
        $this->imageModifierFuncName = $funcName;
332
        $this->params = $params;
333
334
        return $this;
335
    }
336
337
    /**
338
     * Gets the error code
339
     * @return int
340
     */
341
    public function getErrorCode(): int
342
    {
343
        return $this->errorCode;
344
    }
345
346
    /**
347
     * Gets the error message from code
348
     * @return string
349
     */
350
    public function getErrorMessage(): string
351
    {
352
        return $this->errorMessages[$this->errorCode];
353
    }
354
355
    /**
356
     * Tells whether the file was uploaded
357
     * @return bool
358
     */
359
    public function isUploaded(): bool
360
    {
361
        return is_uploaded_file($this->getPathname());
362
    }
363
364
    /**
365
     * Checks if the given file is image
366
     * @param $filePath
367
     * @return bool
368
     */
369
    public function isImage($filePath): bool
370
    {
371
        return !!getimagesize($filePath);
372
    }
373
374
    /**
375
     * Moves an uploaded file to a new location
376
     * @param string $filePath
377
     * @return bool
378
     */
379
    protected function moveUploadedFile(string $filePath): bool
380
    {
381
        if ($this->remoteFileSystem) {
382
            return (bool)$this->remoteFileSystem->put($filePath, $this->localFileSystem->get($this->getPathname()));
0 ignored issues
show
Bug introduced by
It seems like $this->localFileSystem->get($this->getPathname()) can also be of type false; however, parameter $content of Quantum\Libraries\Storag...AdapterInterface::put() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

382
            return (bool)$this->remoteFileSystem->put($filePath, /** @scrutinizer ignore-type */ $this->localFileSystem->get($this->getPathname()));
Loading history...
383
        } else {
384
            if ($this->isUploaded()) {
385
                return move_uploaded_file($this->getPathname(), $filePath);
386
            } else {
387
                return $this->localFileSystem->copy($this->getPathname(), $filePath);
388
            }
389
        }
390
    }
391
392
    /**
393
     * Validates upload against allowed mime types => extensions map
394
     * @param string $extension
395
     * @param string $mimeType
396
     * @return bool
397
     */
398
    protected function allowed(string $extension, string $mimeType): bool
399
    {
400
        $extension = strtolower($extension);
401
        $mimeType = strtolower($mimeType);
402
403
        return isset($this->allowedMimeTypes[$mimeType]) &&
404
            in_array($extension, $this->allowedMimeTypes[$mimeType], true);
405
    }
406
407
    /**
408
     * Loads allowed mime types from config (shared/config/uploads.php) if present.
409
	 * @return void
410
     * @throws FileUploadException
411
     */
412
    protected function loadAllowedMimeTypesFromConfig(): void
413
    {
414
		if (!config()->has('uploads')) {
415
			config()->import(new Setup('config', 'uploads'));
416
		}
417
418
        $allowedMimeTypesMap = config()->get('uploads.allowed_mime_types');
419
420
        if ($allowedMimeTypesMap !== null && !is_array($allowedMimeTypesMap)) {
421
            throw FileUploadException::incorrectMimeTypesConfig('uploads');
422
        }
423
424
        if (is_array($allowedMimeTypesMap)) {
425
            $this->setAllowedMimeTypesMap($allowedMimeTypesMap);
426
        }
427
    }
428
429
    /**
430
     * Sets the allowed mime types => extensions map
431
     * @param array $allowedMimeTypes
432
     * @param bool $merge
433
     * @return void
434
     */
435
    protected function setAllowedMimeTypesMap(array $allowedMimeTypes, bool $merge = true): void
436
    {
437
        if ($merge) {
438
            $this->allowedMimeTypes = array_merge_recursive($this->allowedMimeTypes, $allowedMimeTypes);
439
        } else {
440
            $this->allowedMimeTypes = $allowedMimeTypes;
441
        }
442
    }
443
444
    /**
445
     * Applies modifications on image
446
     * @param $filePath
447
     * @throws ImageResizeException
448
     */
449
    protected function applyModifications($filePath)
450
    {
451
        $image = new ImageResize($filePath);
452
        call_user_func_array([$image, $this->imageModifierFuncName], $this->params);
453
454
        $image->save($filePath);
455
    }
456
}