Passed
Push — develop ( 185343...eb895f )
by Daniel
06:30
created

ApiNormalizer   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 197
Duplicated Lines 0 %

Test Coverage

Coverage 85.92%

Importance

Changes 0
Metric Value
dl 0
loc 197
ccs 61
cts 71
cp 0.8592
rs 9.6
c 0
b 0
f 0
wmc 32

10 Methods

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