Completed
Push — develop ( b2caa8...6f6e85 )
by Daniel
07:40
created

ApiNormalizer::getFileData()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 6.0014

Importance

Changes 0
Metric Value
cc 6
eloc 29
nc 4
nop 3
dl 0
loc 41
ccs 28
cts 29
cp 0.9655
crap 6.0014
rs 8.8337
c 0
b 0
f 0
1
<?php
2
3
namespace Silverback\ApiComponentBundle\Serializer;
4
5
use ApiPlatform\Core\Api\IriConverterInterface;
6
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
7
use Doctrine\ORM\EntityManagerInterface;
8
use function file_exists;
9
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
10
use Silverback\ApiComponentBundle\Entity\Content\Component\Collection\Collection;
11
use Silverback\ApiComponentBundle\Entity\Content\Component\ComponentLocation;
12
use Silverback\ApiComponentBundle\Entity\Content\Component\Form\Form;
13
use Silverback\ApiComponentBundle\Entity\Content\Dynamic\AbstractDynamicPage;
14
use Silverback\ApiComponentBundle\Entity\Content\FileInterface;
15
use Silverback\ApiComponentBundle\Entity\Content\Page;
16
use Silverback\ApiComponentBundle\Entity\Layout\Layout;
17
use Silverback\ApiComponentBundle\Factory\Entity\Content\Component\Form\FormViewFactory;
18
use Silverback\ApiComponentBundle\Imagine\PathResolver;
19
use Symfony\Component\Routing\RouterInterface;
20
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
21
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
22
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
23
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
24
use Symfony\Component\Serializer\Serializer;
25
use Symfony\Component\Serializer\SerializerAwareInterface;
26
use Symfony\Component\Serializer\SerializerInterface;
27
28
final class ApiNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface
29
{
30
    private $decorated;
31
    private $imagineCacheManager;
32
    private $formViewFactory;
33
    private $pathResolver;
34
    private $em;
35
    private $projectDir;
36
    private $collectionDataProvider;
37
    private $serializer;
38
    private $router;
39
    private $iriConverter;
40
41
    /**
42
     * FileNormalizer constructor.
43
     * @param NormalizerInterface $decorated
44
     * @param CacheManager $imagineCacheManager
45
     * @param FormViewFactory $formViewFactory
46
     * @param PathResolver $pathResolver
47
     * @param EntityManagerInterface $entityManager
48
     * @param ContextAwareCollectionDataProviderInterface $collectionDataProvider
49
     * @param RouterInterface $router
50
     * @param IriConverterInterface $iriConverter
51
     * @param string $projectDir
52
     */
53 10
    public function __construct(
54
        NormalizerInterface $decorated,
55
        CacheManager $imagineCacheManager,
56
        FormViewFactory $formViewFactory,
57
        PathResolver $pathResolver,
58
        EntityManagerInterface $entityManager,
59
        ContextAwareCollectionDataProviderInterface $collectionDataProvider,
60
        RouterInterface $router,
61
        IriConverterInterface $iriConverter,
62
        string $projectDir
63
    ) {
64 10
        if (!$decorated instanceof DenormalizerInterface) {
65
            throw new \InvalidArgumentException(sprintf('The decorated normalizer must implement the %s.', DenormalizerInterface::class));
66
        }
67
        if (!$decorated instanceof AbstractNormalizer) {
68 10
            throw new \InvalidArgumentException(sprintf('The decorated normalizer must implement the %s.', AbstractNormalizer::class));
69 10
        }
70 10
        // If a page will list itself again as a component then we should re-serialize it as it'll have different context/groups applied
71 10
        $decorated->setCircularReferenceLimit(2);
72 10
        $this->decorated = $decorated;
73 10
        $this->imagineCacheManager = $imagineCacheManager;
74 10
        $this->formViewFactory = $formViewFactory;
75 10
        $this->pathResolver = $pathResolver;
76 10
        $this->em = $entityManager;
77 10
        $this->collectionDataProvider = $collectionDataProvider;
78
        $this->router = $router;
79 10
        $this->iriConverter = $iriConverter;
80 10
        $this->projectDir = $projectDir;
81 10
82
        $normalizers = array(new ObjectNormalizer());
83
        $this->serializer = new Serializer($normalizers);
84
    }
85
86
    /**
87
     * @param mixed $data
88 5
     * @param string|null $format
89
     * @return bool
90 5
     */
91
    public function supportsNormalization($data, $format = null): bool
92
    {
93
        return $this->decorated->supportsNormalization($data, $format);
94
    }
95
96
    /**
97
     * @param $object
98
     * @param string|null $format
99
     * @param array $context
100
     * @return array|bool|float|int|string
101
     * @throws \Symfony\Component\Serializer\Exception\LogicException
102
     * @throws \Symfony\Component\Serializer\Exception\InvalidArgumentException
103 2
     * @throws \Symfony\Component\Serializer\Exception\CircularReferenceException
104
     * @throws \ApiPlatform\Core\Exception\ResourceClassNotSupportedException
105 2
     */
106
    public function normalize($object, $format = null, array $context = [])
107
    {
108
        if (($object instanceof Page || $object instanceof AbstractDynamicPage) && !$object->getLayout()) {
109 2
            // Should we be using the ItemDataProvider (or detect data provider and use that, we already use a custom data provider for layouts)
110
            $object->setLayout($this->em->getRepository(Layout::class)->findOneBy(['default' => true]));
111
        }
112 2
        if ($object instanceof AbstractDynamicPage) {
113
            $object = $this->populateDynamicComponents($object);
114
        }
115
        if ($object instanceof Collection) {
116 2
            // We should really find whatever the data provider is currently for the resource instead of just using the default
117 1
            $object->setCollection($this->collectionDataProvider->getCollection($object->getResource(), 'GET', $context));
118
        }
119
        if ($object instanceof Form && !$object->getForm()) {
120 2
            $object->setForm($this->formViewFactory->create($object));
121
        }
122 2
123 1
        $data = $this->decorated->normalize($object, $format, $context);
124
        // data may be a string if circular reference
125 2
        if (\is_array($data) && $object instanceof FileInterface) {
126
            $data = array_merge($data, $this->getFileData($object, $format, $context));
127
        }
128
        return $data;
129
    }
130
131
    /**
132
     * @param \Silverback\ApiComponentBundle\Entity\Content\FileInterface $object
133
     * @param null|string $format
134 1
     * @param array $context
135
     * @return array
136 1
     */
137 1
    private function getFileData(FileInterface $object, $format = null, array $context = []): array
138 1
    {
139 1
        $data = [];
140 1
        $filePath = $object->getFilePath();
141 1
        if ($filePath && file_exists($filePath)) {
142 1
            $objectId = $this->iriConverter->getIriFromItem($object);
143
            $data['file:publicPath'] = $this->router->generate(
144
                'files_upload',
145 1
                [ 'field' => 'filePath', 'id' => $objectId ]
146 1
            );
147 1
            // $this->getPublicPath($filePath);
148 1
            if (\exif_imagetype($filePath)) {
149 1
                $data['file:image'] = $this->serializer->normalize(
150
                    new ImageMetadata($filePath, $data['file:publicPath']),
151 1
                    $format,
152 1
                    $context
153 1
                );
154 1
                $supported = $this->isImagineSupportedFile($filePath);
155
                if ($supported) {
156
                    $imagineData = [];
157 1
                    foreach ($object::getImagineFilters() as $returnKey => $filter) {
158 1
                        // Strip path root from beginning of string.
159 1
                        // Whatever image roots are set in imagine will be looped and removed from the start of the string
160 1
                        $resolvedPath = $this->pathResolver->resolve($filePath);
161 1
                        $imagineBrowserPath = $this->imagineCacheManager->getBrowserPath($resolvedPath, $filter);
162 1
                        $imagineFilePath = ltrim(parse_url(
163 1
                            $imagineBrowserPath,
164 1
                            PHP_URL_PATH
165 1
                        ), '/');
166 1
                        $realPath = sprintf('%s/public/%s', $this->projectDir, $imagineFilePath);
167 1
                        $imagineData[$returnKey] = $this->serializer->normalize(
168
                            new ImageMetadata($realPath, $imagineFilePath, $filter),
169
                            $format,
170 1
                            $context
171
                        );
172
                    }
173
                    $data['file:imagine'] = $imagineData;
174 1
                }
175
            }
176
        }
177
        return $data;
178
    }
179
180
    /**
181
     * @param mixed $data
182
     * @param string $type
183
     * @param string|null $format
184
     * @return bool
185
     */
186
    public function supportsDenormalization($data, $type, $format = null): bool
187
    {
188
        return $this->decorated->supportsDenormalization($data, $type, $format);
189
    }
190
191
    /**
192
     * @param mixed $data
193
     * @param string $class
194 5
     * @param string|null $format
195
     * @param array $context
196 5
     * @return object
197
     * @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
198
     * @throws \Symfony\Component\Serializer\Exception\RuntimeException
199
     * @throws \Symfony\Component\Serializer\Exception\LogicException
200
     * @throws \Symfony\Component\Serializer\Exception\InvalidArgumentException
201
     * @throws \Symfony\Component\Serializer\Exception\ExtraAttributesException
202
     * @throws \Symfony\Component\Serializer\Exception\BadMethodCallException
203
     * @throws \InvalidArgumentException
204
     */
205
    public function denormalize($data, $class, $format = null, array $context = [])
206
    {
207
        $context['allow_extra_attributes'] = $class === Form::class;
208
        return $this->decorated->denormalize($data, $class, $format, $context);
209
    }
210
211
    /**
212
     * @param SerializerInterface $serializer
213 1
     */
214
    public function setSerializer(SerializerInterface $serializer)
215 1
    {
216 1
        if ($this->decorated instanceof SerializerAwareInterface) {
217
            $this->decorated->setSerializer($serializer);
218
        }
219
    }
220
221
    /**
222 5
     * @param string $filePath
223
     * @return bool
224 5
     */
225 5
    public function isImagineSupportedFile(?string $filePath): bool
226
    {
227 5
        if (!$filePath) {
228
            return false;
229
        }
230
        try {
231
            $imageType = \exif_imagetype($filePath);
232
        } catch (\Exception $e) {
233 3
            return false;
234
        }
235 3
        return \in_array($imageType, [IMAGETYPE_JPEG, IMAGETYPE_JPEG2000, IMAGETYPE_PNG, IMAGETYPE_GIF], true);
236 1
    }
237
238
    /**
239 3
     * @param AbstractDynamicPage $page
240 1
     * @return AbstractDynamicPage
241 1
     */
242
    public function populateDynamicComponents(AbstractDynamicPage $page): AbstractDynamicPage
243 3
    {
244
        $locations = $this->em->getRepository(ComponentLocation::class)->findByDynamicPage($page);
245
        if ($locations) {
246
            $page->setComponentLocations($locations);
247
        }
248
        return $page;
249
    }
250
}
251