Passed
Push — master ( 3f7190...385f04 )
by Adrien
13:37
created

AbstractFile::generateUniqueFilename()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Model\Traits;
6
7
use Doctrine\ORM\Mapping as ORM;
8
use Exception;
9
use GraphQL\Doctrine\Attribute as API;
10
use Psr\Http\Message\UploadedFileInterface;
11
12
/**
13
 * Wrapping class for an uploaded file.
14
 */
15
trait AbstractFile
16
{
17
    /**
18
     * Get base path where the files are stored in the server.
19
     */
20
    abstract protected function getBasePath(): string;
21
22
    /**
23
     * Get list of accepted MIME types.
24
     *
25
     * @return string[]
26
     */
27
    abstract protected function getAcceptedMimeTypes(): array;
28
29
    #[ORM\Column(type: 'string', length: 190, options: ['default' => ''])]
30
    #[API\Exclude]
31
    private string $filename = '';
32
33
    #[ORM\Column(type: 'string', length: 255, options: ['default' => ''])]
34
    private string $mime = '';
35
36
    /**
37
     * Set the file.
38
     */
39 6
    public function setFile(UploadedFileInterface $file): void
40
    {
41 6
        $this->generateUniqueFilename($file->getClientFilename() ?? '');
42
43 6
        $path = $this->getPath();
44 6
        if (file_exists($path)) {
45
            throw new Exception('A file already exist with the same name: ' . $this->getFilename());
46
        }
47 6
        $file->moveTo($path);
48
49 6
        $this->validateMimeType();
50
    }
51
52
    /**
53
     * Set filename (without path).
54
     */
55 8
    #[API\Exclude]
56
    public function setFilename(string $filename): void
57
    {
58 8
        $this->filename = $filename;
59
    }
60
61
    /**
62
     * Get filename (without path).
63
     */
64 8
    #[API\Exclude]
65
    public function getFilename(): string
66
    {
67 8
        return $this->filename;
68
    }
69
70
    /**
71
     * Get mime.
72
     */
73 6
    public function getMime(): string
74
    {
75 6
        return $this->mime;
76
    }
77
78
    /**
79
     * Get absolute path to file on disk.
80
     */
81 2
    #[API\Exclude]
82
    public function getPath(): string
83
    {
84 2
        return realpath('.') . '/' . $this->getBasePath() . $this->getFilename();
85
    }
86
87
    /**
88
     * Automatically called by Doctrine when the object is deleted
89
     * Is called after database update because we can have issues on remove operation (like integrity test)
90
     * and it's preferable to keep a related file on drive before removing it definitely.
91
     */
92
    #[ORM\PostRemove]
93
    public function deleteFile(): void
94
    {
95
        $path = $this->getPath();
96
        if (file_exists($path) && is_file($path) && !str_contains($this->getFilename(), 'dw4jV3zYSPsqE2CB8BcP8ABD0.')) {
97
            unlink($path);
98
        }
99
    }
100
101
    /**
102
     * Generate unique filename while trying to preserve original extension.
103
     */
104 6
    private function generateUniqueFilename(string $originalFilename): void
105
    {
106 6
        $extension = pathinfo($originalFilename, PATHINFO_EXTENSION);
107 6
        $filename = uniqid() . ($extension ? '.' . $extension : '');
0 ignored issues
show
Bug introduced by
Are you sure $extension of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

107
        $filename = uniqid() . ($extension ? '.' . /** @scrutinizer ignore-type */ $extension : '');
Loading history...
108 6
        $this->setFilename($filename);
109
    }
110
111
    /**
112
     * Delete file and throw exception if MIME type is invalid.
113
     */
114 6
    final protected function validateMimeType(): void
115
    {
116 6
        $path = $this->getPath();
117 6
        $mime = mime_content_type($path);
118 6
        if ($mime === false) {
119
            throw new Exception('Could not get mimetype for path: ' . $path);
120
        }
121
122 6
        if ($mime === 'image/svg') {
123
            $mime = 'image/svg+xml';
124
        }
125
126
        // Validate mimetype
127 6
        if (!in_array($mime, $this->getAcceptedMimeTypes(), true)) {
128
            unlink($path);
129
130
            throw new Exception('Invalid file type of: ' . $mime);
131
        }
132
133 6
        $this->mime = $mime;
134
    }
135
}
136