Completed
Push — master ( 40e412...07993c )
by Tobias
02:30
created

MetadataAwareDenormalizer   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 220
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 7
Bugs 1 Features 1
Metric Value
wmc 38
c 7
b 1
f 1
lcom 1
cbo 5
dl 0
loc 220
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
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Serial...rializerAwareNormalizer has been deprecated with message: since version 3.1, to be removed in 4.0. Use the SerializerAwareTrait instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
18
{
19
    use GroupValidationTrait;
20
    
21
    /**
22
     * @var Metadata[]
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
    public function __construct(array $metadata, PropertyNameConverter $pnc)
36
    {
37
        $this->metadata = $metadata;
38
        $this->propertyNameConverter = $pnc;
39
    }
40
41
    /**
42
     * {@inheritdoc}
43
     */
44
    public function denormalize($data, $class, $format = null, array $context = array())
45
    {
46
        $meta = $this->getMetadata($class);
47
        $normalizedData = (array) $data;
48
        $object = $this->getInstance($class, $context);
49
50
        foreach ($normalizedData as $attribute => $value) {
51
            if (null === $propertyName = $this->getPropertyName($meta, $attribute)) {
0 ignored issues
show
Documentation introduced by
$meta is of type object<Happyr\SerializerBundle\Metadata\Metadata>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
52
                // Name is invalid, skip this
53
                continue;
54
            }
55
56
            if (null !== $value && !is_scalar($value)) {
57
                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
                if (null !== $propertyType = $this->getPropertyType($meta, $propertyName)) {
0 ignored issues
show
Documentation introduced by
$meta is of type object<Happyr\SerializerBundle\Metadata\Metadata>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
62
                    $value = $this->serializer->denormalize($value, $propertyType, $format, $context);
63
                }
64
            }
65
66
            $this->setPropertyValue($object, $meta, $propertyName, $value, $context);
0 ignored issues
show
Documentation introduced by
$meta is of type object<Happyr\SerializerBundle\Metadata\Metadata>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
67
        }
68
69
        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
    private function setPropertyValue($object, array $meta, $propertyName, $value, array $context)
82
    {
83
        if (!isset($meta['property'][$propertyName])) {
84
            $meta['property'][$propertyName] = [];
85
        }
86
87
        // Default exclusion policy is NONE
88
        $exclusionPolicy = isset($meta['class']['exclusion_policy']) ? $meta['class']['exclusion_policy'] : ExclusionPolicy::NONE;
89
90
        // If this property should be in the output
91
        $included = $exclusionPolicy === ExclusionPolicy::NONE;
92
93
        // If read_only is defined and true
94
        $readOnly = false;
95
        if (isset($meta['class']['read_only']) ? $meta['class']['read_only'] : false) {
96
            $readOnly = true;
97
        }
98
99
        $groups = ['Default'];
100
        $accessor = null;
101
        foreach ($meta['property'][$propertyName] as $name => $metaValue) {
102
            switch ($name) {
103
                case 'exclude':
104
                    // Skip this
105
                    return;
106
                case 'read_only':
107
                    if ($metaValue === true) {
108
                        return;
109
                    }
110
                    // If readOnly = false we should include this
111
                    $readOnly = false;
112
                    break;
113
                case 'expose':
114
                    $included = true;
115
                    break;
116
                case 'accessor':
117
                    if (isset($metaValue['setter'])) {
118
                        $accessor = $metaValue['setter'];
119
                    }
120
                    break;
121
                case 'groups':
122
                    $groups = $metaValue;
123
                    break;
124
            }
125
        }
126
127
        // Validate context groups
128
        if (!empty($context['groups'])) {
129
            $included = $this->includeBasedOnGroup($context, $groups);
130
        }
131
132
        if (!$included || $readOnly) {
133
            return;
134
        }
135
136
        if ($accessor) {
137
            $object->$accessor($value);
138
        } else {
139
            ReflectionPropertyAccess::set($object, $propertyName, $value);
140
        }
141
    }
142
143
    /**
144
     * Get instance of the class.
145
     *
146
     * @param string $class
147
     * @param array  $context
148
     *
149
     * @return object
150
     */
151
    private function getInstance($class, array $context)
152
    {
153
        if (
154
            isset($context[AbstractNormalizer::OBJECT_TO_POPULATE]) &&
155
            is_object($context[AbstractNormalizer::OBJECT_TO_POPULATE]) &&
156
            $context[AbstractNormalizer::OBJECT_TO_POPULATE] instanceof $class
157
        ) {
158
            $object = $context[AbstractNormalizer::OBJECT_TO_POPULATE];
159
            unset($context[AbstractNormalizer::OBJECT_TO_POPULATE]);
160
161
            return $object;
162
        }
163
164
        $reflectionClass = new \ReflectionClass($class);
165
        $constructor = $reflectionClass->getConstructor();
166
        if (!$constructor) {
167
            return new $class();
168
        }
169
170
        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
    private function getPropertyName($rootMeta, $serializedName)
182
    {
183
        $propertyName = $this->propertyNameConverter->getPropertyName($rootMeta, $serializedName);
184
185
        $meta = isset($rootMeta['property'][$propertyName]) ? $rootMeta['property'][$propertyName] : [];
186
        $verify = $this->propertyNameConverter->getSerializedName($meta, $propertyName);
187
188
        if ($serializedName === $verify) {
189
            return $propertyName;
190
        }
191
192
        // The $serializedName was fake
193
        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
    private function getPropertyType($meta, $name)
205
    {
206
        foreach ($meta['property'][$name] as $metaName => $value) {
207
            if ($metaName === 'type') {
208
                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
    public function supportsDenormalization($data, $type, $format = null)
223
    {
224
        return isset($this->metadata[$type]);
225
    }
226
227
    /**
228
     * @param string $class
229
     *
230
     * @return Metadata|mixed
231
     */
232
    private function getMetadata($class)
233
    {
234
        return $this->metadata[$class];
235
    }
236
}
237