Passed
Pull Request — main (#161)
by Daniel
05:38 queued 01:35
created

MediaObjectFactory::createFromImagine()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 9
nc 2
nop 3
dl 0
loc 17
ccs 0
cts 10
cp 0
crap 6
rs 9.9666
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 TemporaryUrlGenerator) {
75
//                // $filesystem->temporaryUrl();
76
//            }
77
78
            $urlGenerator = $this->urlGenerators->get($fieldConfiguration->urlGenerator);
79
            if (!$urlGenerator instanceof UploadableUrlGeneratorInterface) {
80
                throw new InvalidArgumentException(sprintf('The url generator provided must implement %s', UploadableUrlGeneratorInterface::class));
81
            }
82
            $contentUrl = $urlGenerator->generateUrl($object, $fileProperty, $filesystem, $path);
83
84
            // Populate the primary MediaObject
85
            try {
86
                $propertyMediaObjects[] = $this->create($filesystem, $path, $contentUrl);
87
            } catch (UnableToReadFile $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
88
            }
89
90
            array_push($propertyMediaObjects, ...$this->getMediaObjectsForImagineFilters($object, $path, $fieldConfiguration, $fileProperty));
91
92
            $collection->set($fileProperty, $propertyMediaObjects);
93
        }
94
95
        return $collection->count() ? $collection : null;
96
    }
97
98
    /**
99
     * @return MediaObject[]
100
     */
101
    private function getMediaObjectsForImagineFilters(object $object, string $path, UploadableField $uploadableField, string $fileProperty): array
102
    {
103
        $mediaObjects = [];
104
        if (!$this->filterService) {
105
            return $mediaObjects;
106
        }
107
108
        // Let the data loader which should be configured for imagine to know which adapter to use
109
        $this->flysystemDataLoader->setAdapter($uploadableField->adapter);
110
111
        $filters = $uploadableField->imagineFilters;
112
        if ($object instanceof ImagineFiltersInterface) {
113
            $request = $this->requestStack->getMainRequest();
114
            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

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