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

ApiNormalizer::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3.0021

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 3
nop 9
dl 0
loc 31
ccs 15
cts 16
cp 0.9375
crap 3.0021
rs 9.7333
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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