Test Failed
Push — develop ( 8ff4ed...2e815f )
by Daniel
04:44
created

FileUploader::getNewFilename()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
nc 2
nop 2
dl 0
loc 13
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace Silverback\ApiComponentBundle\File\Uploader;
4
5
use ApiPlatform\Core\Validator\ValidatorInterface;
6
use Doctrine\ORM\EntityManagerInterface;
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Silverback\ApiComponentBundle\Entity\Component\FileInterface;
10
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
11
use Symfony\Component\Filesystem\Filesystem;
12
use Symfony\Component\HttpFoundation\File\File;
13
use Symfony\Component\HttpFoundation\File\UploadedFile;
14
use Symfony\Component\PropertyAccess\PropertyAccess;
15
16
class FileUploader
17
{
18
    private $rootPath;
19
    private $validator;
20
    private $propertyAccessor;
21
    private $em;
22
23
    public function __construct(
24
        ValidatorInterface $validator,
25
        EntityManagerInterface $em,
26
        array $rootPaths = []
27
    ) {
28
        $this->validator = $validator;
29
        $this->rootPath = $rootPaths['uploads'];
30
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
31
        $this->em = $em;
32
    }
33
34
    private function getRealPath(string $moveToDir, string $filename): string
35
    {
36
        return rtrim($moveToDir, '/') . '/' . $filename;
37
    }
38
39
    private function getNewFilename(string $moveToDir, UploadedFile $file): string
40
    {
41
        $fs = new Filesystem();
42
43
        $ext = $file->guessExtension();
44
        $basename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
45
        $filename = "$basename.$ext";
46
        $i = 0;
47
        while ($fs->exists($this->getRealPath($moveToDir, $filename))) {
48
            $i++;
49
            $filename = "$basename.$i.$ext";
50
        }
51
        return $filename;
52
    }
53
54
    private function validateNewFile($entity, $field, UploadedFile $file): void
55
    {
56
        $this->propertyAccessor->setValue($entity, $field, $file);
57
        $errors = $this->validator->validate($entity);
58
        if ($errors !== null && \count($errors)) {
59
            throw new InvalidArgumentException((string) $errors);
60
        }
61
    }
62
63
    private function unlinkFile(File $currentFile): void
64
    {
65
        $oldFilePath = $currentFile->getRealPath();
66
        if (!is_writable($oldFilePath)) {
67
            throw new RuntimeException('The existing file cannot be deleted. File upload aborted');
68
        }
69
        unlink($currentFile->getRealPath());
70
    }
71
72
    public function upload(FileInterface $entity, string $field, UploadedFile $file): FileInterface
73
    {
74
        /** @var File|null|string $currentFile */
75
        $currentFile = $this->propertyAccessor->getValue($entity, $field);
76
77
        // Set to the new file and validate it before we upload and persist any changes
78
        $this->validateNewFile($entity, $field, $file);
79
80
        // Validation passed, remove old file first (in case we don't have permission to do it)
81
        if ($currentFile) {
82
            try {
83
                $this->unlinkFile(new File($currentFile));
84
            } catch (FileNotFoundException $e) {
85
                // If the file did not exist, there's no problem if it was not found as we are trying to delete it anyway
86
            }
87
        }
88
        // Old file removed, let's update!
89
        $moveToDir = sprintf('%s/%s', $this->rootPath, $entity->getDir());
90
        $filename = $this->getNewFilename($moveToDir, $file);
91
        $movedFile = $file->move($moveToDir, $filename);
92
        $this->propertyAccessor->setValue($entity, $field, $movedFile->getRealPath());
93
        $this->em->flush();
94
        return $entity;
95
    }
96
}
97