JmsSerializerSubscriber::getHostUrl()   B
last analyzed

Complexity

Conditions 7
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 8.8333
c 0
b 0
f 0
cc 7
nc 3
nop 0
1
<?php
2
/*
3
 * This file is part of the FreshVichUploaderSerializationBundle
4
 *
5
 * (c) Artem Henvald <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Fresh\VichUploaderSerializationBundle\EventListener;
14
15
use Doctrine\Common\Annotations\Reader;
16
use Doctrine\Common\Util\ClassUtils;
17
use Doctrine\Persistence\Proxy;
18
use Fresh\VichUploaderSerializationBundle\Annotation\VichSerializableClass;
19
use Fresh\VichUploaderSerializationBundle\Annotation\VichSerializableField;
20
use Fresh\VichUploaderSerializationBundle\Exception\IncompatibleUploadableAndSerializableFieldAnnotationException;
21
use JMS\Serializer\EventDispatcher\Events;
22
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
23
use JMS\Serializer\EventDispatcher\ObjectEvent;
24
use JMS\Serializer\EventDispatcher\PreSerializeEvent;
25
use Psr\Log\LoggerInterface;
26
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
27
use Symfony\Component\Routing\RequestContext;
28
use Vich\UploaderBundle\Mapping\Annotation\UploadableField;
29
use Vich\UploaderBundle\Storage\StorageInterface;
30
31
/**
32
 * JmsSerializerSubscriber.
33
 *
34
 * @author Artem Henvald <[email protected]>
35
 */
36
class JmsSerializerSubscriber implements EventSubscriberInterface
37
{
38
    private const HTTP_PORT = 80;
39
40
    private const HTTPS_PORT = 443;
41
42
    /** @var StorageInterface */
43
    private $storage;
44
45
    /** @var RequestContext */
46
    private $requestContext;
47
48
    /** @var Reader */
49
    private $annotationReader;
50
51
    /** @var PropertyAccessorInterface */
52
    private $propertyAccessor;
53
54
    /** @var LoggerInterface */
55
    private $logger;
56
57
    /** @var mixed[] */
58
    private $serializedObjects = [];
59
60
    /**
61
     * @param StorageInterface          $storage
62
     * @param RequestContext            $requestContext
63
     * @param Reader                    $annotationReader
64
     * @param PropertyAccessorInterface $propertyAccessor
65
     * @param LoggerInterface           $logger
66
     */
67
    public function __construct(StorageInterface $storage, RequestContext $requestContext, Reader $annotationReader, PropertyAccessorInterface $propertyAccessor, LoggerInterface $logger)
68
    {
69
        $this->storage = $storage;
70
        $this->requestContext = $requestContext;
71
        $this->annotationReader = $annotationReader;
72
        $this->propertyAccessor = $propertyAccessor;
73
        $this->logger = $logger;
74
    }
75
76
    /**
77
     * @return iterable|array[]
78
     */
79
    public static function getSubscribedEvents(): iterable
80
    {
81
        yield ['event' => Events::PRE_SERIALIZE, 'method' => 'onPreSerialize'];
82
        yield ['event' => Events::POST_SERIALIZE, 'method' => 'onPostSerialize'];
83
    }
84
85
    /**
86
     * @param PreSerializeEvent $event
87
     *
88
     * @throws IncompatibleUploadableAndSerializableFieldAnnotationException
89
     */
90
    public function onPreSerialize(PreSerializeEvent $event): void
91
    {
92
        $object = $event->getObject();
93
        if (!\is_object($object)) {
94
            return;
95
        }
96
97
        if ($object instanceof Proxy && !$object->__isInitialized()) {
98
            $object->__load();
99
        }
100
101
        $objectUid = \spl_object_hash($object);
102
        if (\array_key_exists($objectUid, $this->serializedObjects)) {
103
            return;
104
        }
105
106
        /** @var class-string<object> $className */
0 ignored issues
show
Documentation introduced by
The doc-type class-string<object> could not be parsed: Unknown type name "class-string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
107
        $className = ClassUtils::getClass($object);
108
109
        $classAnnotation = $this->annotationReader->getClassAnnotation(
110
            new \ReflectionClass($className),
111
            VichSerializableClass::class
112
        );
113
114
        if ($classAnnotation instanceof VichSerializableClass) {
115
            $reflectionClass = ClassUtils::newReflectionClass(\get_class($object));
116
            $this->logger->debug(\sprintf(
117
                'Found @VichSerializableClass annotation for the class "%s"',
118
                $reflectionClass->getName()
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
119
            ));
120
121
            foreach ($reflectionClass->getProperties() as $property) {
122
                $vichSerializableAnnotation = $this->annotationReader->getPropertyAnnotation($property, VichSerializableField::class);
123
124
                if ($vichSerializableAnnotation instanceof VichSerializableField) {
125
                    $vichUploadableFileAnnotation = $this->annotationReader->getPropertyAnnotation($property, UploadableField::class);
126
127
                    if ($vichUploadableFileAnnotation instanceof UploadableField) {
128
                        $exceptionMessage = \sprintf(
129
                            'The field "%s" in the class "%s" cannot have @UploadableField and @VichSerializableField annotations at the same moment.',
130
                            $property->getName(),
131
                            $reflectionClass->getName()
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
132
                        );
133
134
                        throw new IncompatibleUploadableAndSerializableFieldAnnotationException($exceptionMessage);
135
                    }
136
                    $this->logger->debug(\sprintf(
137
                        'Found @VichSerializableField annotation for the field "%s" in the class "%s"',
138
                        $property->getName(),
139
                        $reflectionClass->getName()
0 ignored issues
show
Bug introduced by
Consider using $reflectionClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
140
                    ));
141
142
                    $uri = null;
143
                    $property->setAccessible(true);
144
145
                    if ($property->getValue($event->getObject())) {
146
                        $uri = $this->storage->resolveUri($object, $vichSerializableAnnotation->getField());
147
                        if ($vichSerializableAnnotation->isIncludeHost() && false === \filter_var($uri, FILTER_VALIDATE_URL)) {
148
                            $uri = $this->getHostUrl().$uri;
149
                        }
150
                    }
151
                    $this->serializedObjects[$objectUid][$property->getName()] = $property->getValue($event->getObject());
152
                    $property->setValue($object, $uri);
153
                }
154
            }
155
        }
156
    }
157
158
    /**
159
     * @param ObjectEvent $event
160
     */
161
    public function onPostSerialize(ObjectEvent $event): void
162
    {
163
        $object = $event->getObject();
164
        if (!\is_object($object)) {
165
            return;
166
        }
167
168
        if ($object instanceof Proxy && !$object->__isInitialized()) {
169
            $object->__load();
170
        }
171
172
        $objectUid = \spl_object_hash($object);
173
        if (!\array_key_exists($objectUid, $this->serializedObjects)) {
174
            return;
175
        }
176
177
        foreach ($this->serializedObjects[$objectUid] as $propertyName => $propertyValue) {
178
            $this->propertyAccessor->setValue($object, $propertyName, $propertyValue);
179
        }
180
        unset($this->serializedObjects[$objectUid]);
181
    }
182
183
    /**
184
     * Get host url (scheme://host:port).
185
     *
186
     * @return string
187
     */
188
    private function getHostUrl(): string
189
    {
190
        $scheme = $this->requestContext->getScheme();
191
        $url = $scheme.'://'.$this->requestContext->getHost();
192
193
        $httpPort = $this->requestContext->getHttpPort();
194
        if ('http' === $scheme && $httpPort && self::HTTP_PORT !== $httpPort) {
195
            return $url.':'.$httpPort;
196
        }
197
198
        $httpsPort = $this->requestContext->getHttpsPort();
199
        if ('https' === $scheme && $httpsPort && self::HTTPS_PORT !== $httpsPort) {
200
            return $url.':'.$httpsPort;
201
        }
202
203
        return $url;
204
    }
205
}
206