Passed
Pull Request — main (#161)
by Daniel
04:14
created

MediaObjectFactory::create()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 20
nc 7
nop 3
dl 0
loc 30
ccs 0
cts 20
cp 0
crap 42
rs 8.9777
c 1
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\Factory\Uploadable;
15
16
use Doctrine\Common\Collections\ArrayCollection;
17
use Doctrine\Persistence\ManagerRegistry;
18
use League\Flysystem\Filesystem;
19
use League\Flysystem\UnableToReadFile;
20
use Liip\ImagineBundle\Service\FilterService;
21
use Silverback\ApiComponentsBundle\Annotation\UploadableField;
22
use Silverback\ApiComponentsBundle\AttributeReader\UploadableAttributeReader;
23
use Silverback\ApiComponentsBundle\Entity\Core\FileInfo;
24
use Silverback\ApiComponentsBundle\Entity\Utility\ImagineFiltersInterface;
25
use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException;
26
use Silverback\ApiComponentsBundle\Flysystem\FilesystemProvider;
27
use Silverback\ApiComponentsBundle\Helper\Uploadable\FileInfoCacheManager;
28
use Silverback\ApiComponentsBundle\Imagine\FlysystemDataLoader;
29
use Silverback\ApiComponentsBundle\Model\Uploadable\MediaObject;
30
use Silverback\ApiComponentsBundle\Utility\ClassMetadataTrait;
31
use Symfony\Component\DependencyInjection\ServiceLocator;
32
use Symfony\Component\HttpFoundation\RequestStack;
33
34
/**
35
 * @author Daniel West <[email protected]>
36
 */
37
class MediaObjectFactory
38
{
39
    use ClassMetadataTrait;
40
41
    public function __construct(
42
        ManagerRegistry $managerRegistry,
43
        private readonly FileInfoCacheManager $fileInfoCacheManager,
44
        private readonly UploadableAttributeReader $annotationReader,
45
        private readonly FilesystemProvider $filesystemProvider,
46
        private readonly FlysystemDataLoader $flysystemDataLoader,
47
        private readonly RequestStack $requestStack,
48
        private readonly ServiceLocator $urlGenerators,
49
        private readonly ?FilterService $filterService = null
50
    ) {
51
        $this->initRegistry($managerRegistry);
52
    }
53
54
    public function createMediaObjects(object $object): ?ArrayCollection
55
    {
56
        $collection = new ArrayCollection();
57
        $classMetadata = $this->getClassMetadata($object);
58
59
        $configuredProperties = $this->annotationReader->getConfiguredProperties($object, true);
60
61
        foreach ($configuredProperties as $fileProperty => $fieldConfiguration) {
62
            $propertyMediaObjects = [];
63
            // todo: we may need to look at the performance of this when getting the components. yes, the response is cached, but even first load on a page with lots of files, could be very bad
64
            $filesystem = $this->filesystemProvider->getFilesystem($fieldConfiguration->adapter);
65
            $path = $classMetadata->getFieldValue($object, $fieldConfiguration->property);
66
            if (!$path) {
67
                continue;
68
            }
69
            if (!$filesystem->fileExists($path)) {
70
                continue;
71
            }
72
73
            // todo: the content URL perhaps will just be a public URL from the source/CDN instead of via this API download action
74
//            if ($filesystem instanceof PublicUrlGenerator) {
75
//                // $filesystem->publicUrl();
76
//            }
77
//            if ($filesystem instanceof TemporaryUrlGenerator) {
78
//                // $filesystem->temporaryUrl();
79
//            }
80
81
            $urlGenerator = $this->urlGenerators->get($fieldConfiguration->urlGenerator);
82
            if (!$urlGenerator instanceof UploadableUrlGeneratorInterface) {
83
                throw new InvalidArgumentException(sprintf('The url generator provided must implement %s', UploadableUrlGeneratorInterface::class));
84
            }
85
            $contentUrl = $urlGenerator->generateUrl($object, $fileProperty);
86
87
            // Populate the primary MediaObject
88
            try {
89
                $propertyMediaObjects[] = $this->create($filesystem, $path, $contentUrl);
90
            } catch (UnableToReadFile $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
91
            }
92
93
            array_push($propertyMediaObjects, ...$this->getMediaObjectsForImagineFilters($object, $path, $fieldConfiguration, $fileProperty));
94
95
            $collection->set($fileProperty, $propertyMediaObjects);
96
        }
97
98
        return $collection->count() ? $collection : null;
99
    }
100
101
    /**
102
     * @return MediaObject[]
103
     */
104
    private function getMediaObjectsForImagineFilters(object $object, string $path, UploadableField $uploadableField, string $fileProperty): array
105
    {
106
        $mediaObjects = [];
107
        if (!$this->filterService) {
108
            return $mediaObjects;
109
        }
110
111
        // Let the data loader which should be configured for imagine to know which adapter to use
112
        $this->flysystemDataLoader->setAdapter($uploadableField->adapter);
113
114
        $filters = $uploadableField->imagineFilters;
115
        if ($object instanceof ImagineFiltersInterface) {
116
            $request = $this->requestStack->getMainRequest();
117
            array_push($filters, ...$object->getImagineFilters($fileProperty, $request));
0 ignored issues
show
Bug introduced by
It seems like $filters can also be of type null; however, parameter $array of array_push() does only seem to accept array, 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

117
            array_push(/** @scrutinizer ignore-type */ $filters, ...$object->getImagineFilters($fileProperty, $request));
Loading history...
118
        }
119
120
        foreach ($filters as $filter) {
121
            $resolvedUrl = $this->filterService->getUrlOfFilteredImage($path, $filter);
122
            $mediaObjects[] = $this->createFromImagine($resolvedUrl, $path, $filter);
123
        }
124
125
        return $mediaObjects;
126
    }
127
128
    private function create(Filesystem $filesystem, string $filename, string $contentUrl): MediaObject
129
    {
130
        $mediaObject = new MediaObject();
131
132
        $mediaObject->contentUrl = $contentUrl;
133
        $mediaObject->imagineFilter = null;
134
135
        $fileInfo = $this->fileInfoCacheManager->resolveCache($filename);
136
        if ($fileInfo) {
137
            return $this->populateMediaObjectFromCache($mediaObject, $fileInfo);
138
        }
139
140
        $mediaObject->fileSize = $filesystem->fileSize($filename);
141
        $mediaObject->mimeType = $filesystem->mimeType($filename);
142
        if (str_contains($mediaObject->mimeType, 'image/')) {
143
            $file = str_replace("\0", '', $filesystem->read($filename));
144
            if ('image/svg+xml' === $mediaObject->mimeType) {
145
                $xmlGet = simplexml_load_string($file);
146
                $xmlAttributes = $xmlGet->attributes();
147
                $mediaObject->width = $xmlAttributes ? (int) $xmlAttributes->width : null;
148
                $mediaObject->height = $xmlAttributes ? (int) $xmlAttributes->height : null;
149
            } else {
150
                [$mediaObject->width, $mediaObject->height] = @getimagesize($file);
151
            }
152
        }
153
154
        $fileInfo = new FileInfo($filename, $mediaObject->mimeType, $mediaObject->fileSize, $mediaObject->width, $mediaObject->height);
155
        $this->fileInfoCacheManager->saveCache($fileInfo);
156
157
        return $mediaObject;
158
    }
159
160
    private function createFromImagine(string $contentUrl, string $path, string $imagineFilter): MediaObject
161
    {
162
        $mediaObject = new MediaObject();
163
        $mediaObject->contentUrl = $contentUrl;
164
        $mediaObject->imagineFilter = $imagineFilter;
165
166
        $fileInfo = $this->fileInfoCacheManager->resolveCache($path, $imagineFilter);
167
        if ($fileInfo) {
168
            return $this->populateMediaObjectFromCache($mediaObject, $fileInfo);
169
        }
170
171
        // todo: check why we are setting this, from imagine we should know this info I'm guessing
172
        // todo: should we not save the info to cache as well as above?
173
        $mediaObject->width = $mediaObject->height = $mediaObject->fileSize = -1;
174
        $mediaObject->mimeType = '';
175
176
        return $mediaObject;
177
    }
178
179
    private function populateMediaObjectFromCache(MediaObject $mediaObject, FileInfo $fileInfo): MediaObject
180
    {
181
        $mediaObject->fileSize = $fileInfo->fileSize;
182
        $mediaObject->mimeType = $fileInfo->mimeType;
183
        $mediaObject->width = $fileInfo->width;
184
        $mediaObject->height = $fileInfo->height;
185
186
        return $mediaObject;
187
    }
188
}
189