Passed
Push — develop ( a2aeae...9ee07e )
by Daniel
05:41
created

ApiNormalizer::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 2.0011

Importance

Changes 0
Metric Value
cc 2
eloc 14
nc 2
nop 9
dl 0
loc 28
ccs 14
cts 15
cp 0.9333
crap 2.0011
rs 9.7998
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 AbstractNormalizer $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
        AbstractNormalizer $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) {
0 ignored issues
show
introduced by
$decorated is always a sub-type of Symfony\Component\Serial...r\DenormalizerInterface.
Loading history...
65
            throw new \InvalidArgumentException(sprintf('The decorated normalizer must implement the %s.', DenormalizerInterface::class));
66
        }
67
        // If a page will list itself again as a component then we should re-serialize it as it'll have different context/groups applied
68 10
        $decorated->setCircularReferenceLimit(2);
69 10
        $this->decorated = $decorated;
70 10
        $this->imagineCacheManager = $imagineCacheManager;
71 10
        $this->formViewFactory = $formViewFactory;
72 10
        $this->pathResolver = $pathResolver;
73 10
        $this->em = $entityManager;
74 10
        $this->collectionDataProvider = $collectionDataProvider;
75 10
        $this->router = $router;
76 10
        $this->iriConverter = $iriConverter;
77 10
        $this->projectDir = $projectDir;
78
79 10
        $normalizers = array(new ObjectNormalizer());
80 10
        $this->serializer = new Serializer($normalizers);
81 10
    }
82
83
    /**
84
     * @param mixed $data
85
     * @param string|null $format
86
     * @return bool
87
     */
88 5
    public function supportsNormalization($data, $format = null): bool
89
    {
90 5
        return $this->decorated->supportsNormalization($data, $format);
91
    }
92
93
    /**
94
     * @param $object
95
     * @param string|null $format
96
     * @param array $context
97
     * @return array|bool|float|int|string
98
     * @throws \Symfony\Component\Serializer\Exception\LogicException
99
     * @throws \Symfony\Component\Serializer\Exception\InvalidArgumentException
100
     * @throws \Symfony\Component\Serializer\Exception\CircularReferenceException
101
     * @throws \ApiPlatform\Core\Exception\ResourceClassNotSupportedException
102
     */
103 2
    public function normalize($object, $format = null, array $context = [])
104
    {
105 2
        if (($object instanceof Page || $object instanceof AbstractDynamicPage) && !$object->getLayout()) {
106
            // Should we be using the ItemDataProvider (or detect data provider and use that, we already use a custom data provider for layouts)
107
            $object->setLayout($this->em->getRepository(Layout::class)->findOneBy(['default' => true]));
108
        }
109 2
        if ($object instanceof AbstractDynamicPage) {
110
            $object = $this->populateDynamicComponents($object);
111
        }
112 2
        if ($object instanceof Collection) {
113
            // We should really find whatever the data provider is currently for the resource instead of just using the default
114
            $object->setCollection($this->collectionDataProvider->getCollection($object->getResource(), 'GET', $context));
115
        }
116 2
        if ($object instanceof Form && !$object->getForm()) {
117 1
            $object->setForm($this->formViewFactory->create($object));
118
        }
119
120 2
        $data = $this->decorated->normalize($object, $format, $context);
121
        // data may be a string if circular reference
122 2
        if (\is_array($data) && $object instanceof FileInterface) {
123 1
            $data = array_merge($data, $this->getFileData($object, $format, $context));
124
        }
125 2
        return $data;
126
    }
127
128
    /**
129
     * @param \Silverback\ApiComponentBundle\Entity\Content\FileInterface $object
130
     * @param null|string $format
131
     * @param array $context
132
     * @return array
133
     */
134 1
    private function getFileData(FileInterface $object, $format = null, array $context = []): array
135
    {
136 1
        $data = [];
137 1
        $filePath = $object->getFilePath();
138 1
        if ($filePath && file_exists($filePath)) {
139 1
            $objectId = $this->iriConverter->getIriFromItem($object);
140 1
            $data['file:publicPath'] = $this->router->generate(
141 1
                'files_upload',
142 1
                [ 'field' => 'filePath', 'id' => $objectId ]
143
            );
144
            // $this->getPublicPath($filePath);
145 1
            if (\exif_imagetype($filePath)) {
146 1
                $data['file:image'] = $this->serializer->normalize(
147 1
                    new ImageMetadata($filePath, $data['file:publicPath']),
148 1
                    $format,
149 1
                    $context
150
                );
151 1
                $supported = $this->isImagineSupportedFile($filePath);
152 1
                if ($supported) {
153 1
                    $imagineData = [];
154 1
                    foreach ($object::getImagineFilters() as $returnKey => $filter) {
155
                        // Strip path root from beginning of string.
156
                        // Whatever image roots are set in imagine will be looped and removed from the start of the string
157 1
                        $resolvedPath = $this->pathResolver->resolve($filePath);
158 1
                        $imagineBrowserPath = $this->imagineCacheManager->getBrowserPath($resolvedPath, $filter);
159 1
                        $imagineFilePath = ltrim(parse_url(
160 1
                            $imagineBrowserPath,
161 1
                            PHP_URL_PATH
162 1
                        ), '/');
163 1
                        $realPath = sprintf('%s/public/%s', $this->projectDir, $imagineFilePath);
164 1
                        $imagineData[$returnKey] = $this->serializer->normalize(
165 1
                            new ImageMetadata($realPath, $imagineFilePath, $filter),
166 1
                            $format,
167 1
                            $context
168
                        );
169
                    }
170 1
                    $data['file:imagine'] = $imagineData;
171
                }
172
            }
173
        }
174 1
        return $data;
175
    }
176
177
    private function getPublicPath(string $filePath)
0 ignored issues
show
Unused Code introduced by
The method getPublicPath() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
178
    {
179
        $publicPaths = [$this->projectDir, '/public/'];
180
        foreach ($publicPaths as $path) {
181
            if (mb_strpos($filePath, $path) === 0 && $start = \strlen($path)) {
182
                $filePath = mb_substr($filePath, $start);
183
            }
184
        }
185
        return $filePath;
186
    }
187
188
    /**
189
     * @param mixed $data
190
     * @param string $type
191
     * @param string|null $format
192
     * @return bool
193
     */
194 5
    public function supportsDenormalization($data, $type, $format = null): bool
195
    {
196 5
        return $this->decorated->supportsDenormalization($data, $type, $format);
197
    }
198
199
    /**
200
     * @param mixed $data
201
     * @param string $class
202
     * @param string|null $format
203
     * @param array $context
204
     * @return object
205
     * @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
206
     * @throws \Symfony\Component\Serializer\Exception\RuntimeException
207
     * @throws \Symfony\Component\Serializer\Exception\LogicException
208
     * @throws \Symfony\Component\Serializer\Exception\InvalidArgumentException
209
     * @throws \Symfony\Component\Serializer\Exception\ExtraAttributesException
210
     * @throws \Symfony\Component\Serializer\Exception\BadMethodCallException
211
     * @throws \InvalidArgumentException
212
     */
213 1
    public function denormalize($data, $class, $format = null, array $context = [])
214
    {
215 1
        $context['allow_extra_attributes'] = $class === Form::class;
216 1
        return $this->decorated->denormalize($data, $class, $format, $context);
217
    }
218
219
    /**
220
     * @param SerializerInterface $serializer
221
     */
222 5
    public function setSerializer(SerializerInterface $serializer)
223
    {
224 5
        if ($this->decorated instanceof SerializerAwareInterface) {
0 ignored issues
show
introduced by
$this->decorated is always a sub-type of Symfony\Component\Serial...erializerAwareInterface.
Loading history...
225 5
            $this->decorated->setSerializer($serializer);
226
        }
227 5
    }
228
229
    /**
230
     * @param string $filePath
231
     * @return bool
232
     */
233 3
    public function isImagineSupportedFile(?string $filePath): bool
234
    {
235 3
        if (!$filePath) {
236 1
            return false;
237
        }
238
        try {
239 3
            $imageType = \exif_imagetype($filePath);
240 1
        } catch (\Exception $e) {
241 1
            return false;
242
        }
243 3
        return \in_array($imageType, [IMAGETYPE_JPEG, IMAGETYPE_JPEG2000, IMAGETYPE_PNG, IMAGETYPE_GIF], true);
244
    }
245
246
    /**
247
     * @param AbstractDynamicPage $page
248
     * @return AbstractDynamicPage
249
     */
250
    public function populateDynamicComponents(AbstractDynamicPage $page): AbstractDynamicPage
251
    {
252
        $locations = $this->em->getRepository(ComponentLocation::class)->findByDynamicPage($page);
253
        if ($locations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
254
            $page->setComponentLocations($locations);
255
        }
256
        return $page;
257
    }
258
}
259