Ecodev /
felix
| 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 | 8 | public function setFile(UploadedFileInterface $file): void |
|
| 40 | { |
||
| 41 | 8 | $this->generateUniqueFilename($file->getClientFilename() ?? ''); |
|
| 42 | |||
| 43 | 8 | $path = $this->getPath(); |
|
| 44 | 8 | if (file_exists($path)) { |
|
| 45 | throw new Exception('A file already exist with the same name: ' . $this->getFilename()); |
||
| 46 | } |
||
| 47 | 8 | $file->moveTo($path); |
|
| 48 | |||
| 49 | 8 | $this->validateMimeType(); |
|
| 50 | } |
||
| 51 | |||
| 52 | /** |
||
| 53 | * Set filename (without path). |
||
| 54 | */ |
||
| 55 | 10 | #[API\Exclude] |
|
| 56 | public function setFilename(string $filename): void |
||
| 57 | { |
||
| 58 | 10 | $this->filename = $filename; |
|
| 59 | } |
||
| 60 | |||
| 61 | /** |
||
| 62 | * Get filename (without path). |
||
| 63 | */ |
||
| 64 | 10 | #[API\Exclude] |
|
| 65 | public function getFilename(): string |
||
| 66 | { |
||
| 67 | 10 | return $this->filename; |
|
| 68 | } |
||
| 69 | |||
| 70 | /** |
||
| 71 | * Get mime. |
||
| 72 | */ |
||
| 73 | 8 | public function getMime(): string |
|
| 74 | { |
||
| 75 | 8 | 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 before the object is deleted. |
||
| 89 | * |
||
| 90 | * Without this, the record would not exist anymore in DB when we try to |
||
| 91 | * get the file path in `deleteFile()`. And then we could not delete the |
||
| 92 | * file on disk after the record deletion. |
||
| 93 | */ |
||
| 94 | #[ORM\PreRemove] |
||
| 95 | public function triggerLazyLoading(): void |
||
| 96 | { |
||
| 97 | $this->getPath(); |
||
| 98 | } |
||
| 99 | |||
| 100 | /** |
||
| 101 | * Automatically called by Doctrine when the object is deleted |
||
| 102 | * Is called after database update because we can have issues on remove operation (like integrity test) |
||
| 103 | * and it's preferable to keep a related file on drive before removing it definitely. |
||
| 104 | */ |
||
| 105 | #[ORM\PostRemove] |
||
| 106 | public function deleteFile(): void |
||
| 107 | { |
||
| 108 | $path = $this->getPath(); |
||
| 109 | if (file_exists($path) && is_file($path) && !str_contains($this->getFilename(), 'dw4jV3zYSPsqE2CB8BcP8ABD0.')) { |
||
| 110 | unlink($path); |
||
| 111 | } |
||
| 112 | } |
||
| 113 | |||
| 114 | /** |
||
| 115 | * Generate unique filename while trying to preserve original extension. |
||
| 116 | */ |
||
| 117 | 8 | private function generateUniqueFilename(string $originalFilename): void |
|
| 118 | { |
||
| 119 | 8 | $extension = pathinfo($originalFilename, PATHINFO_EXTENSION); |
|
| 120 | 8 | $filename = uniqid() . ($extension ? '.' . $extension : ''); |
|
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 121 | 8 | $this->setFilename($filename); |
|
| 122 | } |
||
| 123 | |||
| 124 | /** |
||
| 125 | * Delete file and throw exception if MIME type is invalid. |
||
| 126 | */ |
||
| 127 | 8 | final protected function validateMimeType(): void |
|
| 128 | { |
||
| 129 | 8 | $path = $this->getPath(); |
|
| 130 | 8 | $mime = mime_content_type($path); |
|
| 131 | 8 | if ($mime === false) { |
|
| 132 | throw new Exception('Could not get mimetype for path: ' . $path); |
||
| 133 | } |
||
| 134 | |||
| 135 | 8 | if ($mime === 'image/svg') { |
|
| 136 | $mime = 'image/svg+xml'; |
||
| 137 | } |
||
| 138 | |||
| 139 | // Validate mimetype |
||
| 140 | 8 | if (!in_array($mime, $this->getAcceptedMimeTypes(), true)) { |
|
| 141 | unlink($path); |
||
| 142 | |||
| 143 | throw new Exception('Invalid file type of: ' . $mime); |
||
| 144 | } |
||
| 145 | |||
| 146 | 8 | $this->mime = $mime; |
|
| 147 | } |
||
| 148 | } |
||
| 149 |