Passed
Push — master ( 685595...680e76 )
by Julien
01:32
created

Serializer::setSdk()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 2
cts 2
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
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
0 ignored issues
show
introduced by
$identifierAttribute is of type Mapado\RestClientSdk\Mapping\Attribute, thus it always evaluated to true.
Loading history...
93 1
            ? $identifierAttribute->getSerializedKey()
94 1
            : null;
95
96 1
        $attributeList = $classMetadata->getAttributeList();
97
98 1
        $instance = new $className();
99
100 1
        foreach ($attributeList as $attribute) {
101 1
            $key = $attribute->getSerializedKey();
102
103 1
            if (!ArrayHelper::arrayHas($data, $key)) {
104 1
                continue;
105
            }
106
107 1
            $value = ArrayHelper::arrayGet($data, $key);
108
109 1
            $setter = 'set' . ucfirst($attribute->getAttributeName());
110
111 1
            if (method_exists($instance, $setter)) {
112 1
                $relation = $classMetadata->getRelation($key);
113 1
                if ($relation) {
114 1
                    if (is_string($value)) {
115 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

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