MetadataAwareDenormalizer   B
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 220
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 90.32%

Importance

Changes 8
Bugs 2 Features 1
Metric Value
wmc 38
c 8
b 2
f 1
lcom 1
cbo 5
dl 0
loc 220
ccs 84
cts 93
cp 0.9032
rs 8.3999

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
C denormalize() 0 27 7
D setPropertyValue() 0 61 17
B getInstance() 0 21 5
A getPropertyName() 0 14 3
A getPropertyType() 0 10 3
A supportsDenormalization() 0 4 1
A getMetadata() 0 4 1
1
<?php
2
3
namespace Happyr\SerializerBundle\Normalizer;
4
5
use Happyr\SerializerBundle\Annotation\ExclusionPolicy;
6
use Happyr\SerializerBundle\Metadata\Metadata;
7
use Happyr\SerializerBundle\PropertyManager\ReflectionPropertyAccess;
8
use Happyr\SerializerBundle\PropertyManager\PropertyNameConverter;
9
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
10
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
11
use Symfony\Component\Serializer\Normalizer\SerializerAwareNormalizer;
12
use Symfony\Component\Serializer\Exception\LogicException;
13
14
/**
15
 * @author Tobias Nyholm <[email protected]>
16
 */
17
class MetadataAwareDenormalizer extends SerializerAwareNormalizer implements DenormalizerInterface
18
{
19
    use GroupValidationTrait;
20
21
    /**
22
     * @var array
23
     */
24
    private $metadata;
25
26
    /**
27
     * @var PropertyNameConverter
28
     */
29
    private $propertyNameConverter;
30
31
    /**
32
     * @param array                 $metadata
33
     * @param PropertyNameConverter $pnc
34
     */
35 23
    public function __construct(array $metadata, PropertyNameConverter $pnc)
36
    {
37 23
        $this->metadata = $metadata;
38 23
        $this->propertyNameConverter = $pnc;
39 23
    }
40
41
    /**
42
     * {@inheritdoc}
43
     */
44 12
    public function denormalize($data, $class, $format = null, array $context = [])
45
    {
46 12
        $meta = $this->getMetadata($class);
47 12
        $normalizedData = (array) $data;
48 12
        $object = $this->getInstance($class, $context);
49
50 12
        foreach ($normalizedData as $attribute => $value) {
51 12
            if (null === $propertyName = $this->getPropertyName($meta, $attribute)) {
52
                // Name is invalid, skip this
53 1
                continue;
54
            }
55
56 12
            if (null !== $value && !is_scalar($value)) {
57 1
                if (!$this->serializer instanceof DenormalizerInterface) {
58
                    throw new LogicException(sprintf('Cannot denormalize attribute "%s" because injected serializer is not a normalizer', $attribute));
59
                }
60
61 1
                if (null !== $propertyType = $this->getPropertyType($meta, $propertyName)) {
62 1
                    $value = $this->serializer->denormalize($value, $propertyType, $format, $context);
63 1
                }
64 1
            }
65
66 12
            $this->setPropertyValue($object, $meta, $propertyName, $value, $context);
67 12
        }
68
69 12
        return $object;
70
    }
71
72
    /**
73
     * Set the property value.
74
     *
75
     * @param $object
76
     * @param array $meta
77
     * @param $propertyName
78
     * @param $value
79
     * @param array $context
80
     */
81 12
    private function setPropertyValue($object, array $meta, $propertyName, $value, array $context)
82
    {
83 12
        if (!isset($meta['property'][$propertyName])) {
84
            $meta['property'][$propertyName] = [];
85
        }
86
87
        // Default exclusion policy is NONE
88 12
        $exclusionPolicy = isset($meta['class']['exclusion_policy']) ? $meta['class']['exclusion_policy'] : ExclusionPolicy::NONE;
89
90
        // If this property should be in the output
91 12
        $included = $exclusionPolicy === ExclusionPolicy::NONE;
92
93
        // If read_only is defined and true
94 12
        $readOnly = false;
95 12
        if (isset($meta['class']['read_only']) ? $meta['class']['read_only'] : false) {
96 1
            $readOnly = true;
97 1
        }
98
99 12
        $groups = ['Default'];
100 12
        $accessor = null;
101 12
        foreach ($meta['property'][$propertyName] as $name => $metaValue) {
102
            switch ($name) {
103 11
                case 'exclude':
104
                    // Skip this
105 4
                    return;
106 10
                case 'read_only':
107 2
                    if ($metaValue === true) {
108 2
                        return;
109
                    }
110
                    // If readOnly = false we should include this
111 1
                    $readOnly = false;
112 1
                    break;
113 8
                case 'expose':
114 4
                    $included = true;
115 4
                    break;
116 4
                case 'accessor':
117 1
                    if (isset($metaValue['setter'])) {
118 1
                        $accessor = $metaValue['setter'];
119 1
                    }
120 1
                    break;
121 3
                case 'groups':
122 1
                    $groups = $metaValue;
123 1
                    break;
124
            }
125 12
        }
126
127
        // Validate context groups
128 12
        if (!empty($context['groups'])) {
129 1
            $included = $this->includeBasedOnGroup($context, $groups);
130 1
        }
131
132 12
        if (!$included || $readOnly) {
133 4
            return;
134
        }
135
136 12
        if ($accessor) {
137 1
            $object->$accessor($value);
138 1
        } else {
139 11
            ReflectionPropertyAccess::set($object, $propertyName, $value);
140
        }
141 12
    }
142
143
    /**
144
     * Get instance of the class.
145
     *
146
     * @param string $class
147
     * @param array  $context
148
     *
149
     * @return object
150
     */
151 12
    private function getInstance($class, array $context)
152
    {
153
        if (
154 12
            isset($context[AbstractNormalizer::OBJECT_TO_POPULATE]) &&
155 12
            is_object($context[AbstractNormalizer::OBJECT_TO_POPULATE]) &&
156
            $context[AbstractNormalizer::OBJECT_TO_POPULATE] instanceof $class
157 12
        ) {
158
            $object = $context[AbstractNormalizer::OBJECT_TO_POPULATE];
159
            unset($context[AbstractNormalizer::OBJECT_TO_POPULATE]);
160
161
            return $object;
162
        }
163
164 12
        $reflectionClass = new \ReflectionClass($class);
165 12
        $constructor = $reflectionClass->getConstructor();
166 12
        if (!$constructor) {
167 1
            return new $class();
168
        }
169
170 11
        return $reflectionClass->newInstanceWithoutConstructor();
171
    }
172
173
    /**
174
     * Get the property name for this normalized key name. This will aslo verify if the name is correct.
175
     *
176
     * @param array  $rootMeta
177
     * @param string $serializedName
178
     *
179
     * @return string|null
180
     */
181 12
    private function getPropertyName($rootMeta, $serializedName)
182
    {
183 12
        $propertyName = $this->propertyNameConverter->getPropertyName($rootMeta, $serializedName);
184
185 12
        $meta = isset($rootMeta['property'][$propertyName]) ? $rootMeta['property'][$propertyName] : [];
186 12
        $verify = $this->propertyNameConverter->getSerializedName($meta, $propertyName);
187
188 12
        if ($serializedName === $verify) {
189 12
            return $propertyName;
190
        }
191
192
        // The $serializedName was fake
193 1
        return;
194
    }
195
196
    /**
197
     * Get the type of this property.
198
     *
199
     * @param array  $meta
200
     * @param string $name
201
     *
202
     * @return null|string
203
     */
204 1
    private function getPropertyType($meta, $name)
205
    {
206 1
        foreach ($meta['property'][$name] as $metaName => $value) {
207 1
            if ($metaName === 'type') {
208 1
                return $value;
209
            }
210
        }
211
212
        return;
213
    }
214
215
    /**
216
     * @param mixed  $data
217
     * @param string $type
218
     * @param null   $format
219
     *
220
     * @return bool
221
     */
222 12
    public function supportsDenormalization($data, $type, $format = null)
223
    {
224 12
        return isset($this->metadata[$type]);
225
    }
226
227
    /**
228
     * @param string $class
229
     *
230
     * @return array
231
     */
232 12
    private function getMetadata($class)
233
    {
234 12
        return $this->metadata[$class];
235
    }
236
}
237