Passed
Push — master ( a73c0f...44755a )
by Julien
03:04
created

Serializer::deserialize()   D

Complexity

Conditions 18
Paths 12

Size

Total Lines 90
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 48
CRAP Score 18.0207

Importance

Changes 10
Bugs 0 Features 0
Metric Value
cc 18
eloc 52
c 10
b 0
f 0
nc 12
nop 2
dl 0
loc 90
ccs 48
cts 50
cp 0.96
crap 18.0207
rs 4.8666

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
declare(strict_types=1);
4
5
namespace Mapado\RestClientSdk\Model;
6
7
use libphonenumber\PhoneNumber;
8
use libphonenumber\PhoneNumberFormat;
9
use libphonenumber\PhoneNumberUtil;
10
use Mapado\RestClientSdk\Exception\SdkException;
11
use Mapado\RestClientSdk\Helper\ArrayHelper;
12
use Mapado\RestClientSdk\Mapping;
13
use Mapado\RestClientSdk\Mapping\ClassMetadata;
14
use Mapado\RestClientSdk\SdkClient;
15
use Mapado\RestClientSdk\UnitOfWork;
16
17
/**
18
 * Class Serializer
19
 *
20
 * @author Julien Deniau <[email protected]>
21
 */
22
class Serializer
23
{
24
    /**
25
     * mapping
26
     *
27
     * @var Mapping
28
     */
29
    private $mapping;
30
31
    /**
32
     * @var SdkClient
33
     */
34
    private $sdk;
35
36
    /**
37
     * @var UnitOfWork
38
     */
39
    private $unitOfWork;
40
41
    public function __construct(Mapping $mapping, UnitOfWork $unitOfWork)
42
    {
43 1
        $this->mapping = $mapping;
44 1
        $this->unitOfWork = $unitOfWork;
45 1
    }
46
47
    /**
48
     * @required
49
     */
50
    public function setSdk(SdkClient $sdk): self
51
    {
52 1
        $this->sdk = $sdk;
53
54 1
        return $this;
55
    }
56
57
    /**
58
     * serialize entity for POST and PUT
59
     */
60
    public function serialize(
61
        object $entity,
62
        string $modelName,
63
        array $context = []
64
    ): array {
65 1
        $out = $this->recursiveSerialize($entity, $modelName, 0, $context);
66
67 1
        if (is_string($out)) {
68
            throw new \RuntimeException(
69
                'recursiveSerialize should return an array for level 0 of serialization. This should not happen.'
70
            );
71
        }
72
73 1
        return $out;
74
    }
75
76
    public function deserialize(array $data, string $className): object
77
    {
78 1
        $className = $this->resolveRealClassName($data, $className);
79
80 1
        $classMetadata = $this->mapping->getClassMetadata($className);
81
82 1
        $attributeList = $classMetadata->getAttributeList();
83
84 1
        $instance = new $className();
85
86 1
        if ($attributeList) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attributeList of type Mapado\RestClientSdk\Mapping\Attribute[] 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...
87 1
            foreach ($attributeList as $attribute) {
88 1
                $key = $attribute->getSerializedKey();
89
90 1
                if (!ArrayHelper::arrayHas($data, $key)) {
91 1
                    continue;
92
                }
93
94 1
                $value = ArrayHelper::arrayGet($data, $key);
95
96 1
                $setter = 'set' . ucfirst($attribute->getAttributeName());
97
98 1
                if (method_exists($instance, $setter)) {
99 1
                    $relation = $classMetadata->getRelation($key);
100 1
                    if ($relation) {
101 1
                        if (is_string($value)) {
102 1
                            $value = $this->sdk->createProxy($value);
103 1
                        } elseif (is_array($value)) {
104 1
                            $targetEntity = $relation->getTargetEntity();
105 1
                            $relationClassMetadata = $this->mapping->getClassMetadata(
106 1
                                $targetEntity
107
                            );
108
109 1
                            if ($relation->isManyToOne()) {
110 1
                                $value = $this->deserialize(
111 1
                                    $value,
112 1
                                    $relationClassMetadata->getModelName()
113
                                );
114
                            } else {
115
                                // One-To-Many association
116 1
                                $list = [];
117 1
                                foreach ($value as $item) {
118 1
                                    if (is_string($item)) {
119
                                        $list[] = $this->sdk->createProxy(
120
                                            $item
121
                                        );
122 1
                                    } elseif (is_array($item)) {
123 1
                                        $list[] = $this->deserialize(
124 1
                                            $item,
125 1
                                            $relationClassMetadata->getModelName()
126
                                        );
127
                                    }
128
                                }
129
130 1
                                $value = $list;
131
                            }
132
                        }
133
                    }
134
135 1
                    if (isset($value)) {
136 1
                        if ('datetime' === $attribute->getType()) {
137 1
                            $value = new \DateTime($value);
138
                        }
139
140 1
                        $instance->{$setter}($value);
141
                    }
142
                }
143
            }
144
        }
145
146 1
        $classMetadata = $this->getClassMetadata($instance);
147 1
        if ($classMetadata->hasIdentifierAttribute()) {
148 1
            $idGetter = $classMetadata->getIdGetter();
149
150 1
            if ($idGetter) {
151 1
                $callable = [$instance, $idGetter];
152 1
                $identifier = is_callable($callable)
153 1
                    ? call_user_func($callable)
154 1
                    : null;
155
156 1
                if ($identifier) {
157 1
                    $this->unitOfWork->registerClean(
158 1
                        (string) $identifier,
159
                        $instance
160
                    );
161
                }
162
            }
163
        }
164
165 1
        return $instance;
166
    }
167
168
    /**
169
     * If provided class name is abstract (a base class), the real class name (child class)
170
     * may be available in some data fields.
171
     */
172
    private function resolveRealClassName(
173
        array $data,
174
        string $className
175
    ): string {
176 1
        if (!empty($data['@id'])) {
177 1
            $classMetadata = $this->mapping->tryGetClassMetadataById(
178 1
                $data['@id']
179
            );
180
181 1
            if ($classMetadata) {
182 1
                return $classMetadata->getModelName();
183
            }
184
        }
185
186
        // Real class name could also be retrieved from @type property.
187 1
        return $className;
188
    }
189
190
    /**
191
     * @return array|string
192
     */
193
    private function recursiveSerialize(
194
        object $entity,
195
        string $modelName,
196
        int $level = 0,
197
        array $context = []
198
    ) {
199 1
        $classMetadata = $this->mapping->getClassMetadata($modelName);
200
201 1
        if ($level > 0 && empty($context['serializeRelation'])) {
202 1
            if ($classMetadata->hasIdentifierAttribute()) {
203 1
                $tmpId = $entity->{$classMetadata->getIdGetter()}();
204 1
                if ($tmpId) {
205 1
                    return $tmpId;
206
                }
207
            }
208
        }
209
210 1
        $attributeList = $classMetadata->getAttributeList();
211
212 1
        $out = [];
213 1
        if (!empty($attributeList)) {
214 1
            foreach ($attributeList as $attribute) {
215 1
                $method = 'get' . ucfirst($attribute->getAttributeName());
216
217 1
                if ($attribute->isIdentifier() && !$entity->{$method}()) {
218 1
                    continue;
219
                }
220 1
                $relation = $classMetadata->getRelation(
221 1
                    $attribute->getSerializedKey()
222
                );
223
224 1
                $data = $entity->{$method}();
225
226
                if (
227 1
                    null === $data &&
228 1
                    $relation &&
229 1
                    $relation->isManyToOne() &&
230 1
                    $level > 0
231
                ) {
232
                    /*
233
                        We only serialize the root many-to-one relations to prevent, hopefully,
234
                        unlinked and/or duplicated content. For instance, a cart with cartItemList containing
235
                        null values for the cart [{ cart => null, ... }] may lead the creation of
236
                        CartItem entities explicitly bound to a null Cart instead of the created/updated Cart.
237
                     */
238 1
                    continue;
239 1
                } elseif ($data instanceof \DateTime) {
240 1
                    $data = $data->format('c');
241 1
                } elseif (is_object($data) && $data instanceof PhoneNumber) {
242 1
                    $phoneNumberUtil = PhoneNumberUtil::getInstance();
243 1
                    $data = $phoneNumberUtil->format(
244 1
                        $data,
245 1
                        PhoneNumberFormat::INTERNATIONAL
246
                    );
247
                } elseif (
248 1
                    is_object($data) &&
249 1
                    $relation &&
250 1
                    $this->mapping->hasClassMetadata(
251 1
                        $relation->getTargetEntity()
252
                    )
253
                ) {
254 1
                    $relationClassMetadata = $this->mapping->getClassMetadata(
255 1
                        $relation->getTargetEntity()
256
                    );
257
258 1
                    if (!$relationClassMetadata->hasIdentifierAttribute()) {
259 1
                        $data = $this->recursiveSerialize(
260 1
                            $data,
261 1
                            $relation->getTargetEntity(),
262 1
                            $level + 1,
263 1
                            $context
264
                        );
265
                    } else {
266 1
                        $idAttribute = $relationClassMetadata->getIdentifierAttribute();
267
                        $idGetter =
268 1
                            'get' . ucfirst($idAttribute->getAttributeName());
269
270
                        if (
271 1
                            method_exists($data, $idGetter) &&
272 1
                            $data->{$idGetter}()
273
                        ) {
274 1
                            $data = $data->{$idGetter}();
275 1
                        } elseif ($relation->isManyToOne()) {
276 1
                            if ($level > 0) {
277 1
                                continue;
278
                            } else {
279 1
                                throw new SdkException(
280 1
                                    'Case not allowed for now'
281
                                );
282
                            }
283
                        }
284
                    }
285 1
                } elseif (is_array($data)) {
286 1
                    $newData = [];
287 1
                    foreach ($data as $key => $item) {
288 1
                        if ($item instanceof \DateTime) {
289 1
                            $newData[$key] = $item->format('c');
290
                        } elseif (
291 1
                            is_object($item) &&
292 1
                            $relation &&
293 1
                            $this->mapping->hasClassMetadata(
294 1
                                $relation->getTargetEntity()
295
                            )
296
                        ) {
297
                            $serializeRelation =
298 1
                                !empty($context['serializeRelations']) &&
299 1
                                in_array(
300 1
                                    $relation->getSerializedKey(),
301 1
                                    $context['serializeRelations']
302
                                );
303
304 1
                            $newData[$key] = $this->recursiveSerialize(
305 1
                                $item,
306 1
                                $relation->getTargetEntity(),
307 1
                                $level + 1,
308 1
                                ['serializeRelation' => $serializeRelation]
309
                            );
310
                        } else {
311 1
                            $newData[$key] = $item;
312
                        }
313
                    }
314 1
                    $data = $newData;
315
                }
316
317 1
                $key = $attribute->getSerializedKey();
318
319 1
                $out[$key] = $data;
320
            }
321
        }
322
323 1
        return $out;
324
    }
325
326
    private function getClassMetadataFromId(string $id): ?ClassMetadata
327
    {
328
        $key = $this->mapping->getKeyFromId($id);
329
330
        return $this->mapping->getClassMetadataByKey($key);
331
    }
332
333
    private function getClassMetadata(object $entity): ClassMetadata
334
    {
335 1
        return $this->mapping->getClassMetadata(get_class($entity));
336
    }
337
}
338