Passed
Push — develop ( 0112d6...38c008 )
by Daniel
05:14
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.0146

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 3
nop 9
dl 0
loc 31
ccs 15
cts 17
cp 0.8824
crap 3.0146
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 10
        if (!$decorated instanceof AbstractNormalizer) {
68
            throw new \InvalidArgumentException(sprintf('The decorated normalizer must implement the %s.', AbstractNormalizer::class));
69
        }
70
        // 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 10
        $this->router = $router;
79 10
        $this->iriConverter = $iriConverter;
80 10
        $this->projectDir = $projectDir;
81
82 10
        $normalizers = array(new ObjectNormalizer());
83 10
        $this->serializer = new Serializer($normalizers);
84 10
    }
85
86
    /**
87
     * @param mixed $data
88
     * @param string|null $format
89
     * @return bool
90
     */
91 5
    public function supportsNormalization($data, $format = null): bool
92
    {
93 5
        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
     * @throws \Symfony\Component\Serializer\Exception\CircularReferenceException
104
     * @throws \ApiPlatform\Core\Exception\ResourceClassNotSupportedException
105
     */
106 2
    public function normalize($object, $format = null, array $context = [])
107
    {
108 2
        if (($object instanceof Page || $object instanceof AbstractDynamicPage) && !$object->getLayout()) {
109
            // 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 2
        if ($object instanceof Collection) {
116
            // We should really find whatever the data provider is currently for the resource instead of just using the default
117
            $object->setCollection($this->collectionDataProvider->getCollection($object->getResource(), 'GET', $context));
118
        }
119 2
        if ($object instanceof Form && !$object->getForm()) {
120 1
            $object->setForm($this->formViewFactory->create($object));
121
        }
122
123 2
        $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 1
            $data = array_merge($data, $this->getFileData($object, $format, $context));
127
        }
128 2
        return $data;
129
    }
130
131
    /**
132
     * @param \Silverback\ApiComponentBundle\Entity\Content\FileInterface $object
133
     * @param null|string $format
134
     * @param array $context
135
     * @return array
136
     */
137 1
    private function getFileData(FileInterface $object, $format = null, array $context = []): array
138
    {
139 1
        $data = [];
140 1
        $filePath = $object->getFilePath();
141 1
        if ($filePath && file_exists($filePath)) {
142 1
            $objectId = $this->iriConverter->getIriFromItem($object);
143 1
            $data['file:publicPath'] = $this->router->generate(
144 1
                'files_upload',
145 1
                [ 'field' => 'filePath', 'id' => $objectId ]
146
            );
147
            // $this->getPublicPath($filePath);
148 1
            if (\exif_imagetype($filePath)) {
149 1
                $data['file:image'] = $this->serializer->normalize(
150 1
                    new ImageMetadata($filePath, $data['file:publicPath']),
151 1
                    $format,
152 1
                    $context
153
                );
154 1
                $supported = $this->isImagineSupportedFile($filePath);
155 1
                if ($supported) {
156 1
                    $imagineData = [];
157 1
                    foreach ($object::getImagineFilters() as $returnKey => $filter) {
158
                        // Strip path root from beginning of string.
159
                        // 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 1
                            new ImageMetadata($realPath, $imagineFilePath, $filter),
169 1
                            $format,
170 1
                            $context
171
                        );
172
                    }
173 1
                    $data['file:imagine'] = $imagineData;
174
                }
175
            }
176
        }
177 1
        return $data;
178
    }
179
180
    /**
181
     * @param mixed $data
182
     * @param string $type
183
     * @param string|null $format
184
     * @return bool
185
     */
186 1
    public function supportsDenormalization($data, $type, $format = null): bool
187
    {
188 1
        return $this->decorated->supportsDenormalization($data, $type, $format);
189
    }
190
191
    /**
192
     * @param mixed $data
193
     * @param string $class
194
     * @param string|null $format
195
     * @param array $context
196
     * @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 1
    public function denormalize($data, $class, $format = null, array $context = [])
206
    {
207 1
        $context['allow_extra_attributes'] = $class === Form::class;
208 1
        return $this->decorated->denormalize($data, $class, $format, $context);
209
    }
210
211
    /**
212
     * @param SerializerInterface $serializer
213
     */
214 5
    public function setSerializer(SerializerInterface $serializer)
215
    {
216 5
        if ($this->decorated instanceof SerializerAwareInterface) {
217 5
            $this->decorated->setSerializer($serializer);
218
        }
219 5
    }
220
221
    /**
222
     * @param string $filePath
223
     * @return bool
224
     */
225 3
    public function isImagineSupportedFile(?string $filePath): bool
226
    {
227 3
        if (!$filePath) {
228 1
            return false;
229
        }
230
        try {
231 3
            $imageType = \exif_imagetype($filePath);
232 1
        } catch (\Exception $e) {
233 1
            return false;
234
        }
235 3
        return \in_array($imageType, [IMAGETYPE_JPEG, IMAGETYPE_JPEG2000, IMAGETYPE_PNG, IMAGETYPE_GIF], true);
236
    }
237
238
    /**
239
     * @param AbstractDynamicPage $page
240
     * @return AbstractDynamicPage
241
     */
242
    public function populateDynamicComponents(AbstractDynamicPage $page): AbstractDynamicPage
243
    {
244
        $locations = $this->em->getRepository(ComponentLocation::class)->findByDynamicPage($page);
245
        if ($locations) {
246
            $page->setComponentLocations($locations);
247
        }
248
        return $page;
249
    }
250
}
251