Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
PublicLocalFileStorageProvider
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
5 / 5
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 store
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 delete
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 generateUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 supports
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Shared\Infrastructure\Service;
4
5use App\Shared\Domain\Exception\FileStorageException;
6use App\Shared\Domain\Model\StoredFile;
7use App\Shared\Domain\Model\TempFile;
8use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
9use Symfony\Component\DependencyInjection\Attribute\Autowire;
10use Symfony\Component\Filesystem\Filesystem;
11use Symfony\Component\HttpFoundation\File\Exception\FileException;
12use Symfony\Component\String\Slugger\SluggerInterface;
13
14/**
15 * @author Wilhelm Zwertvaegher
16 */
17#[AutoconfigureTag('app.file_storage_provider')]
18final readonly class PublicLocalFileStorageProvider implements FileStorageProvider
19{
20    /**
21     * @var array<string, string>
22     */
23    private array $supportedTypes;
24
25    /**
26     * @param Filesystem $filesystem
27     * @param SluggerInterface $slugger
28     * @param string $uploadsDirectory
29     * @param string $uploadsBaseUrl
30     */
31    public function __construct(
32        private readonly Filesystem $filesystem,
33        private readonly SluggerInterface $slugger,
34        #[Autowire('%public_upload_dir%')] private string $uploadsDirectory,
35        #[Autowire('%public_upload_base_url%')] private string $uploadsBaseUrl
36    ) {
37        $this->supportedTypes = ['user.avatar' => 'user-avatar'];
38    }
39
40    public function store(TempFile $tempFile, string $type): TempFile
41    {
42        $originalFilename = pathinfo($tempFile->getOriginalFilename(), PATHINFO_FILENAME);
43
44        // this is needed to safely include the file name as part of the URL
45        $safeFilename = $this->slugger->slug($originalFilename);
46        $newFilename = $safeFilename.'-'.uniqid().'.'.$tempFile->getExtension();
47        $newFilepath = $this->uploadsDirectory.'/'.$this->supportedTypes[$type].'/'.$newFilename;
48        try {
49            $this->filesystem->copy($tempFile->getPath(), $newFilepath);
50            $this->filesystem->remove($tempFile->getPath());
51        } catch (FileException $e) {
52            throw new FileStorageException('Failed to copy temp file', 0, $e);
53        }
54
55        return new TempFile($newFilename, $tempFile->getOriginalFilename(), $tempFile->getMime(), $tempFile->getExtension());
56    }
57
58    public function delete(StoredFile $storedFile): void
59    {
60        try {
61            $this->filesystem->remove($this->uploadsDirectory.'/'.$this->supportedTypes[$storedFile->getType()].'/'.$storedFile->getPath());
62        } catch (FileException $e) {
63            throw new FileStorageException('Failed to remove stored file', 0, $e);
64        }
65    }
66
67    public function generateUrl(StoredFile $storedFile): string
68    {
69        return $this->uploadsBaseUrl.'/'.$this->supportedTypes[$storedFile->getType()].'/'.$storedFile->getPath();
70    }
71
72    public function supports(string $type): bool
73    {
74        return isset($this->supportedTypes[$type]);
75    }
76}