Completed
Push — develop ( 17b15e...286254 )
by Daniel
08:53
created

ApiNormalizer::populateDynamicComponents()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 7
ccs 0
cts 5
cp 0
crap 6
rs 10
c 0
b 0
f 0
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 AbstractNormalizer $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
            $data['file:publicPath'] = $this->getPublicPath($filePath);
121 1
            if (\exif_imagetype($filePath)) {
122 1
                $data['file:image'] = new ImageMetadata($filePath, $this->getPublicPath($filePath));
123
124 1
                $supported = $this->isImagineSupportedFile($filePath);
125 1
                if ($supported) {
126 1
                    $imagineData = [];
127 1
                    foreach ($object::getImagineFilters() as $returnKey => $filter) {
128
                        // Strip path root from beginning of string.
129
                        // Whatever image roots are set in imagine will be looped and removed from the start of the string
130 1
                        $resolvedPath = $this->pathResolver->resolve($filePath);
131 1
                        $imagineBrowserPath = $this->imagineCacheManager->getBrowserPath($resolvedPath, $filter);
132 1
                        $imagineFilePath = ltrim(parse_url(
133 1
                            $imagineBrowserPath,
134 1
                            PHP_URL_PATH
135 1
                        ), '/');
136
137 1
                        $realPath = sprintf('%s/public/%s', $this->projectDir, $imagineFilePath);
138
//                        dump(
139
//                            [
140
//                                '$resolvedPath' => $resolvedPath,
141
//                                '$imagineBrowserPath' => $imagineBrowserPath,
142
//                                '$imagineFilePath' => $imagineFilePath,
143
//                                '$this->projectDir' => $this->projectDir,
144
//                                '$realPath' => $realPath
145
//                            ]
146
//                        );
147
                        /*
148
                         * array:5 [
149
app_1      |   "$resolvedPath" => "/img/stoney1.jpg"
150
app_1      |   "$imagineBrowserPath" => "http://varnish/media/cache/placeholder_square/img/stoney1.jpg"
151
app_1      |   "$imagineFilePath" => "media/cache/placeholder_square/img/stoney1.jpg"
152
app_1      |   "$this->projectDir" => "/srv/api"
153
app_1      |   "$realPath" => "/srv/api/public/media/cache/placeholder_square/img/stoney1.jpg"
154
app_1      | ]
155
156
                         */
157
158 1
                        $imagineData[$returnKey] = new ImageMetadata($realPath, $imagineFilePath, $filter);
159
                    }
160 1
                    $data['file:imagine'] = $imagineData;
161
                }
162
            }
163
        }
164 1
        return $data;
165
    }
166
167 1
    private function getPublicPath(string $filePath)
168
    {
169 1
        $publicPaths = [$this->projectDir, '/public/'];
170 1
        foreach ($publicPaths as $path) {
171 1
            if (mb_strpos($filePath, $path) === 0 && $start = \strlen($path)) {
172 1
                $filePath = mb_substr($filePath, $start);
173
            }
174
        }
175 1
        return $filePath;
176
    }
177
178
    /**
179
     * @param mixed $data
180
     * @param string $type
181
     * @param string|null $format
182
     * @return bool
183
     */
184 5
    public function supportsDenormalization($data, $type, $format = null): bool
185
    {
186 5
        return $this->decorated->supportsDenormalization($data, $type, $format);
187
    }
188
189
    /**
190
     * @param mixed $data
191
     * @param string $class
192
     * @param string|null $format
193
     * @param array $context
194
     * @return object
195
     * @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
196
     * @throws \Symfony\Component\Serializer\Exception\RuntimeException
197
     * @throws \Symfony\Component\Serializer\Exception\LogicException
198
     * @throws \Symfony\Component\Serializer\Exception\InvalidArgumentException
199
     * @throws \Symfony\Component\Serializer\Exception\ExtraAttributesException
200
     * @throws \Symfony\Component\Serializer\Exception\BadMethodCallException
201
     * @throws \InvalidArgumentException
202
     */
203 1
    public function denormalize($data, $class, $format = null, array $context = [])
204
    {
205 1
        $context['allow_extra_attributes'] = $class === Form::class;
206 1
        return $this->decorated->denormalize($data, $class, $format, $context);
207
    }
208
209
    /**
210
     * @param SerializerInterface $serializer
211
     */
212 5
    public function setSerializer(SerializerInterface $serializer)
213
    {
214 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...
215 5
            $this->decorated->setSerializer($serializer);
216
        }
217 5
    }
218
219
    /**
220
     * @param string $filePath
221
     * @return bool
222
     */
223 3
    public function isImagineSupportedFile(?string $filePath): bool
224
    {
225 3
        if (!$filePath) {
226 1
            return false;
227
        }
228
        try {
229 3
            $imageType = \exif_imagetype($filePath);
230 1
        } catch (\Exception $e) {
231 1
            return false;
232
        }
233 3
        return \in_array($imageType, [IMAGETYPE_JPEG, IMAGETYPE_JPEG2000, IMAGETYPE_PNG, IMAGETYPE_GIF], true);
234
    }
235
236
    /**
237
     * @param AbstractDynamicPage $page
238
     * @return AbstractDynamicPage
239
     */
240
    public function populateDynamicComponents(AbstractDynamicPage $page): AbstractDynamicPage
241
    {
242
        $locations = $this->em->getRepository(ComponentLocation::class)->findByDynamicPage($page);
243
        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...
244
            $page->setComponentLocations($locations);
245
        }
246
        return $page;
247
    }
248
}
249