Passed
Pull Request — master (#67)
by Daniel
05:38
created

UploadableFileManager::persistFiles()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 19
nc 5
nop 1
dl 0
loc 31
ccs 0
cts 20
cp 0
crap 20
rs 9.6333
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Components Bundle Project
5
 *
6
 * (c) Daniel West <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Silverback\ApiComponentsBundle\Helper\Uploadable;
15
16
use Doctrine\Persistence\ManagerRegistry;
17
use Liip\ImagineBundle\Service\FilterService;
18
use Silverback\ApiComponentsBundle\Annotation\UploadableField;
19
use Silverback\ApiComponentsBundle\AnnotationReader\UploadableAnnotationReader;
20
use Silverback\ApiComponentsBundle\Entity\Utility\ImagineFiltersInterface;
21
use Silverback\ApiComponentsBundle\Flysystem\FilesystemProvider;
22
use Silverback\ApiComponentsBundle\Imagine\CacheManager;
23
use Silverback\ApiComponentsBundle\Imagine\FlysystemDataLoader;
24
use Silverback\ApiComponentsBundle\Model\Uploadable\UploadedDataUriFile;
25
use Silverback\ApiComponentsBundle\Utility\ClassMetadataTrait;
26
use Symfony\Component\HttpFoundation\FileBag;
27
use Symfony\Component\HttpFoundation\HeaderUtils;
28
use Symfony\Component\HttpFoundation\Response;
29
use Symfony\Component\HttpFoundation\StreamedResponse;
30
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
31
use Symfony\Component\PropertyAccess\PropertyAccess;
32
33
/**
34
 * @author Daniel West <[email protected]>
35
 */
36
class UploadableFileManager
37
{
38
    use ClassMetadataTrait;
39
40
    private UploadableAnnotationReader $annotationReader;
41
    private FilesystemProvider $filesystemProvider;
42
    private FlysystemDataLoader $flysystemDataLoader;
43
    private FileInfoCacheManager $fileInfoCacheManager;
44
    private ?CacheManager $imagineCacheManager;
45
    private ?FilterService $filterService;
46
47
    public function __construct(ManagerRegistry $registry, UploadableAnnotationReader $annotationReader, FilesystemProvider $filesystemProvider, FlysystemDataLoader $flysystemDataLoader, FileInfoCacheManager $fileInfoCacheManager, ?CacheManager $imagineCacheManager, ?FilterService $filterService = null)
48
    {
49
        $this->initRegistry($registry);
50
        $this->annotationReader = $annotationReader;
51
        $this->filesystemProvider = $filesystemProvider;
52
        $this->flysystemDataLoader = $flysystemDataLoader;
53
        $this->fileInfoCacheManager = $fileInfoCacheManager;
54
        $this->imagineCacheManager = $imagineCacheManager;
55
        $this->filterService = $filterService;
56
    }
57
58
    public function setUploadedFilesFromFileBag(object $object, FileBag $fileBag): void
59
    {
60
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
61
        $configuredProperties = $this->annotationReader->getConfiguredProperties($object, false);
62
63
        /**
64
         * @var UploadableField[] $configuredProperties
65
         */
66
        foreach ($configuredProperties as $fileProperty => $fieldConfiguration) {
67
            if ($file = $fileBag->get($fileProperty, null)) {
68
                $propertyAccessor->setValue($object, $fileProperty, $file);
69
            }
70
        }
71
    }
72
73
    public function storeFilesMetadata(object $object): void
74
    {
75
        $configuredProperties = $this->annotationReader->getConfiguredProperties($object, true);
76
        $classMetadata = $this->getClassMetadata($object);
77
78
        foreach ($configuredProperties as $fileProperty => $fieldConfiguration) {
79
            // Let the data loader which should be configured for imagine to know which adapter to use
80
            $this->flysystemDataLoader->setAdapter($fieldConfiguration->adapter);
81
82
            $filename = $classMetadata->getFieldValue($object, $fieldConfiguration->property);
83
84
            if ($object instanceof ImagineFiltersInterface && $this->filterService) {
85
                $filters = $object->getImagineFilters($fileProperty, null);
86
                foreach ($filters as $filter) {
87
                    // This will trigger the cached file to be store
88
                    // When cached files are store we save the file info
89
                    $this->filterService->getUrlOfFilteredImage($filename, $filter);
90
                }
91
            }
92
        }
93
    }
94
95
    public function persistFiles(object $object): void
96
    {
97
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
98
        $classMetadata = $this->getClassMetadata($object);
99
100
        $configuredProperties = $this->annotationReader->getConfiguredProperties($object, true);
101
        /**
102
         * @var UploadableField[] $configuredProperties
103
         */
104
        foreach ($configuredProperties as $fileProperty => $fieldConfiguration) {
105
            $currentFilepath = $classMetadata->getFieldValue($object, $fieldConfiguration->property);
106
            if ($currentFilepath) {
107
                $this->removeFilepath($object, $fieldConfiguration);
108
            }
109
            /** @var UploadedDataUriFile|null $file */
110
            $file = $propertyAccessor->getValue($object, $fileProperty);
111
            if (!$file) {
112
                $classMetadata->setFieldValue($object, $fieldConfiguration->property, null);
113
                continue;
114
            }
115
116
            $filesystem = $this->filesystemProvider->getFilesystem($fieldConfiguration->adapter);
0 ignored issues
show
Bug introduced by
It seems like $fieldConfiguration->adapter can also be of type null; however, parameter $name of Silverback\ApiComponents...ovider::getFilesystem() 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

116
            $filesystem = $this->filesystemProvider->getFilesystem(/** @scrutinizer ignore-type */ $fieldConfiguration->adapter);
Loading history...
117
118
            $path = $fieldConfiguration->prefix ?? '';
119
            $path .= $file->getFilename();
120
            $stream = fopen($file->getRealPath(), 'r');
121
            $filesystem->writeStream($path, $stream, [
122
                'mimetype' => $file->getMimeType(),
123
            ]);
124
            $classMetadata->setFieldValue($object, $fieldConfiguration->property, $path);
125
            $propertyAccessor->setValue($object, $fileProperty, null);
126
        }
127
    }
128
129
    public function deleteFiles(object $object): void
130
    {
131
        $classMetadata = $this->getClassMetadata($object);
132
133
        $configuredProperties = $this->annotationReader->getConfiguredProperties($object, true);
134
        foreach ($configuredProperties as $fileProperty => $fieldConfiguration) {
135
            $currentFilepath = $classMetadata->getFieldValue($object, $fieldConfiguration->property);
136
            if ($currentFilepath) {
137
                $this->removeFilepath($object, $fieldConfiguration);
138
            }
139
        }
140
    }
141
142
    public function getFileResponse(object $object, string $property, bool $forceDownload = false): Response
143
    {
144
        try {
145
            $reflectionProperty = new \ReflectionProperty($object, $property);
146
        } catch (\ReflectionException $exception) {
147
            throw new NotFoundHttpException($exception->getMessage());
148
        }
149
        if (!$this->annotationReader->isFieldConfigured($reflectionProperty)) {
150
            throw new NotFoundHttpException(sprintf('field configuration not found for %s', $property));
151
        }
152
153
        $propertyConfiguration = $this->annotationReader->getPropertyConfiguration($reflectionProperty);
154
155
        $filesystem = $this->filesystemProvider->getFilesystem($propertyConfiguration->adapter);
0 ignored issues
show
Bug introduced by
It seems like $propertyConfiguration->adapter can also be of type null; however, parameter $name of Silverback\ApiComponents...ovider::getFilesystem() 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

155
        $filesystem = $this->filesystemProvider->getFilesystem(/** @scrutinizer ignore-type */ $propertyConfiguration->adapter);
Loading history...
156
157
        $classMetadata = $this->getClassMetadata($object);
158
159
        $filePath = $classMetadata->getFieldValue($object, $propertyConfiguration->property);
160
161
        $response = new StreamedResponse();
162
        $response->setCallback(static function () use ($filesystem, $filePath) {
163
            $outputStream = fopen('php://output', 'w');
164
            $fileStream = $filesystem->readStream($filePath);
165
            stream_copy_to_stream($fileStream, $outputStream);
0 ignored issues
show
Bug introduced by
It seems like $outputStream can also be of type false; however, parameter $dest of stream_copy_to_stream() does only seem to accept resource, 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

165
            stream_copy_to_stream($fileStream, /** @scrutinizer ignore-type */ $outputStream);
Loading history...
166
        });
167
        $response->headers->set('Content-Type', $filesystem->mimeType($filePath));
168
169
        $disposition = HeaderUtils::makeDisposition($forceDownload ? HeaderUtils::DISPOSITION_ATTACHMENT : HeaderUtils::DISPOSITION_INLINE, $filePath);
170
        $response->headers->set('Content-Disposition', $disposition);
171
172
        return $response;
173
    }
174
175
    private function removeFilepath(object $object, UploadableField $fieldConfiguration): void
176
    {
177
        $classMetadata = $this->getClassMetadata($object);
178
179
        $filesystem = $this->filesystemProvider->getFilesystem($fieldConfiguration->adapter);
0 ignored issues
show
Bug introduced by
It seems like $fieldConfiguration->adapter can also be of type null; however, parameter $name of Silverback\ApiComponents...ovider::getFilesystem() 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

179
        $filesystem = $this->filesystemProvider->getFilesystem(/** @scrutinizer ignore-type */ $fieldConfiguration->adapter);
Loading history...
180
        $currentFilepath = $classMetadata->getFieldValue($object, $fieldConfiguration->property);
181
        $this->fileInfoCacheManager->deleteCaches([$currentFilepath], [null]);
182
        if ($this->imagineCacheManager) {
183
            $this->imagineCacheManager->remove([$currentFilepath], null);
184
        }
185
        if ($filesystem->fileExists($currentFilepath)) {
186
            $filesystem->delete($currentFilepath);
187
        }
188
    }
189
}
190