Completed
Push — master ( b27094...afbc6d )
by Julien
05:50 queued 01:38
created

Serializer::deserialize()   C

Complexity

Conditions 17
Paths 72

Size

Total Lines 71
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 17.0048

Importance

Changes 0
Metric Value
cc 17
eloc 40
nc 72
nop 2
dl 0
loc 71
ccs 38
cts 39
cp 0.9744
crap 17.0048
rs 5.522
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Mapado\RestClientSdk\Model;
4
5
use libphonenumber\PhoneNumber;
6
use libphonenumber\PhoneNumberFormat;
7
use libphonenumber\PhoneNumberUtil;
8
use Mapado\RestClientSdk\Exception\SdkException;
9
use Mapado\RestClientSdk\Helper\ArrayHelper;
10
use Mapado\RestClientSdk\Mapping;
11
use Mapado\RestClientSdk\Mapping\ClassMetadata;
12
use Mapado\RestClientSdk\SdkClient;
13
use Mapado\RestClientSdk\UnitOfWork;
14
15
/**
16
 * Class Serializer
17
 *
18
 * @author Julien Deniau <[email protected]>
19
 */
20
class Serializer
21
{
22
    /**
23
     * mapping
24
     *
25
     * @var Mapping
26
     */
27
    private $mapping;
28
29
    /**
30
     * @var SdkClient|null
31
     */
32
    private $sdk;
33
34
    /**
35
     * @var UnitOfWork
36
     */
37
    private $unitOfWork;
38
39
    /**
40
     * Constructor.
41
     *
42
     * @param Mapping $mapping
43
     */
44
    public function __construct(Mapping $mapping, UnitOfWork $unitOfWork)
45
    {
46 1
        $this->mapping = $mapping;
47 1
        $this->unitOfWork = $unitOfWork;
48 1
    }
49
50
    /**
51
     * setSdk
52
     *
53
     * @param SdkClient $sdk
54
     *
55
     * @return Serializer
56
     */
57
    public function setSdk(SdkClient $sdk)
58
    {
59 1
        $this->sdk = $sdk;
60
61 1
        return $this;
62
    }
63
64
    /**
65
     * serialize entity for POST and PUT
66
     *
67
     * @param object $entity
68
     * @param string $modelName
69
     * @param array  $context
70
     *
71
     * @return array
72
     */
73
    public function serialize($entity, $modelName, $context = [])
74
    {
75 1
        return $this->recursiveSerialize($entity, $modelName, 0, $context);
76
    }
77
78
    /**
79
     * deserialize
80
     *
81
     * @param array  $data
82
     * @param string $className
83
     *
84
     * @return object
85
     */
86
    public function deserialize(array $data, $className)
87
    {
88 1
        $className = $this->resolveRealClassName($data, $className);
89
90 1
        $classMetadata = $this->mapping->getClassMetadata($className);
91 1
        $identifierAttribute = $classMetadata->getIdentifierAttribute();
92 1
        $identifierAttrKey = $identifierAttribute ? $identifierAttribute->getSerializedKey() : null;
0 ignored issues
show
introduced by
$identifierAttribute is of type Mapado\RestClientSdk\Mapping\Attribute, thus it always evaluated to true.
Loading history...
93
94 1
        $attributeList = $classMetadata->getAttributeList();
95
96 1
        $instance = new $className();
97
98 1
        foreach ($attributeList as $attribute) {
99 1
            $key = $attribute->getSerializedKey();
100
101 1
            if (!ArrayHelper::arrayHas($data, $key)) {
102 1
                continue;
103
            }
104
105 1
            $value = ArrayHelper::arrayGet($data, $key);
106
107 1
            $setter = 'set' . ucfirst($attribute->getAttributeName());
108
109 1
            if (method_exists($instance, $setter)) {
110 1
                $relation = $classMetadata->getRelation($key);
111 1
                if ($relation) {
112 1
                    if (is_string($value)) {
113 1
                        $value = $this->sdk->createProxy($value);
0 ignored issues
show
Bug introduced by
The method createProxy() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

113
                        /** @scrutinizer ignore-call */ 
114
                        $value = $this->sdk->createProxy($value);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
114 1
                    } elseif (is_array($value)) {
115 1
                        if (isset($value[$identifierAttrKey])) {
116 1
                            $key = $this->mapping->getKeyFromId($value[$identifierAttrKey]);
0 ignored issues
show
Unused Code introduced by
The assignment to $key is dead and can be removed.
Loading history...
117 1
                            $subClassMetadata = $this->getClassMetadataFromId($value[$identifierAttrKey]);
118 1
                            $value = $this->deserialize($value, $subClassMetadata->getModelName());
119
                        } else {
120 1
                            $list = [];
121 1
                            foreach ($value as $item) {
122 1
                                if (is_string($item)) {
123
                                    $list[] = $this->sdk->createProxy($item);
124 1
                                } elseif (is_array($item) && isset($item[$identifierAttrKey])) {
125
                                    // not tested for now
126
                                    // /the $identifierAttrKey is not the real identifier, as it is
127
                                    // the main object identifier, but we do not have the metadada for now
128
                                    // the thing we assume now is that every entity "may" have the same key
129
                                    // as identifier
130 1
                                    $key = $this->mapping->getKeyFromId($item[$identifierAttrKey]);
131 1
                                    $subClassMetadata = $this->getClassMetadataFromId($item[$identifierAttrKey]);
132 1
                                    $list[] = $this->deserialize($item, $subClassMetadata->getModelName());
133
                                }
134
                            }
135
136 1
                            $value = $list;
137
                        }
138
                    }
139
                }
140
141 1
                if (isset($value)) {
142 1
                    if ($attribute && 'datetime' === $attribute->getType()) {
143 1
                        $value = new \DateTime($value);
144
                    }
145
146 1
                    $instance->$setter($value);
147
                }
148
            }
149
        }
150
151 1
        $identifier = $instance->{$this->getClassMetadata($instance)->getIdGetter()}();
152 1
        if ($identifier) {
153 1
            $this->unitOfWork->registerClean($identifier, $instance);
154
        }
155
156 1
        return $instance;
157
    }
158
159
    /**
160
     * If provided class name is abstract (a base class), the real class name (child class)
161
     * may be available in some data fields.
162
     *
163
     * @param array  $data
164
     * @param string $className
165
     *
166
     * @return string
167
     */
168
    private function resolveRealClassName(array $data, $className)
169
    {
170 1
        if (!empty($data['@id'])) {
171 1
            $classMetadata = $this->mapping->tryGetClassMetadataById($data['@id']);
172
173 1
            if ($classMetadata) {
174 1
                return $classMetadata->getModelName();
175
            }
176
        }
177
178
        // Real class name could also be retrieved from @type property.
179
180 1
        return $className;
181
    }
182
183
    /**
184
     * recursiveSerialize
185
     *
186
     * @param object $entity
187
     * @param string $modelName
188
     * @param int    $level
189
     * @param array  $context
190
     *
191
     * @return array|mixed
192
     */
193
    private function recursiveSerialize($entity, $modelName, $level = 0, $context = [])
194
    {
195 1
        $classMetadata = $this->mapping->getClassMetadata($modelName);
196
197 1
        if ($level > 0 && empty($context['serializeRelation'])) {
198 1
            $idAttribute = $classMetadata->getIdentifierAttribute();
199 1
            $getter = 'get' . ucfirst($idAttribute->getAttributeName());
200 1
            $tmpId = $entity->{$getter}();
201 1
            if ($tmpId) {
202 1
                return $tmpId;
203
            }
204
        }
205
206 1
        $attributeList = $classMetadata->getAttributeList();
207
208 1
        $out = [];
209 1
        if (!empty($attributeList)) {
210 1
            foreach ($attributeList as $attribute) {
211 1
                $method = 'get' . ucfirst($attribute->getAttributeName());
212
213 1
                if ($attribute->isIdentifier() && !$entity->$method()) {
214 1
                    continue;
215
                }
216 1
                $relation = $classMetadata->getRelation($attribute->getSerializedKey());
217
218 1
                $data = $entity->$method();
219
220 1
                if (null === $data && $relation && $relation->isManyToOne() && $level > 0) {
221
                    /*
222
                        We only serialize the root many-to-one relations to prevent, hopefully,
223
                        unlinked and/or duplicated content. For instance, a cart with cartItemList containing
224
                        null values for the cart [{ cart => null, ... }] may lead the creation of
225
                        CartItem entities explicitly bound to a null Cart instead of the created/updated Cart.
226
                     */
227 1
                    continue;
228 1
                } elseif ($data instanceof \DateTime) {
229 1
                    $data = $data->format('c');
230 1
                } elseif (is_object($data) && $data instanceof PhoneNumber) {
231 1
                    $phoneNumberUtil = PhoneNumberUtil::getInstance();
232 1
                    $data = $phoneNumberUtil->format(
233 1
                        $data,
234 1
                        PhoneNumberFormat::INTERNATIONAL
235
                    );
236 1
                } elseif (is_object($data)
237 1
                    && $relation
238 1
                    && $this->mapping->hasClassMetadata($relation->getTargetEntity())
239
                ) {
240 1
                    $idAttribute = $this->mapping
241 1
                        ->getClassMetadata($relation->getTargetEntity())
242 1
                        ->getIdentifierAttribute()
243 1
                        ->getAttributeName();
244 1
                    $idGetter = 'get' . ucfirst($idAttribute);
245
246 1
                    if (method_exists($data, $idGetter) && $data->{$idGetter}()) {
247 1
                        $data = $data->{$idGetter}();
248 1
                    } elseif ($relation->isManyToOne()) {
249 1
                        if ($level > 0) {
250 1
                            continue;
251
                        } else {
252 1
                            throw new SdkException('Case not allowed for now');
253
                        }
254
                    }
255 1
                } elseif (is_array($data)) {
256 1
                    $newData = [];
257 1
                    foreach ($data as $key => $item) {
258 1
                        if ($item instanceof \DateTime) {
259 1
                            $newData[$key] = $item->format('c');
260 1
                        } elseif (is_object($item) &&
261 1
                            $relation &&
262 1
                            $this->mapping->hasClassMetadata($relation->getTargetEntity())
263
                        ) {
264 1
                            $serializeRelation = !empty($context['serializeRelations'])
265 1
                                && in_array($relation->getSerializedKey(), $context['serializeRelations']);
266
267 1
                            $newData[$key] = $this->recursiveSerialize(
268 1
                                $item,
269 1
                                $relation->getTargetEntity(),
270 1
                                $level + 1,
271 1
                                ['serializeRelation' => $serializeRelation]
272
                            );
273
                        } else {
274 1
                            $newData[$key] = $item;
275
                        }
276
                    }
277 1
                    $data = $newData;
278
                }
279
280 1
                $key = $attribute->getSerializedKey();
281
282 1
                $out[$key] = $data;
283
            }
284
        }
285
286 1
        return $out;
287
    }
288
289
    /**
290
     * getClassMetadataFromId
291
     *
292
     * @param string $id
293
     *
294
     * @return ClassMetadata|null
295
     */
296
    private function getClassMetadataFromId($id)
297
    {
298 1
        $key = $this->mapping->getKeyFromId($id);
299 1
        $classMetadata = $this->mapping->getClassMetadataByKey($key);
300
301 1
        return $classMetadata;
302
    }
303
304
    private function getClassMetadata($entity)
305
    {
306 1
        return $this->mapping
307 1
            ->getClassMetadata(get_class($entity));
308
    }
309
}
310