Completed
Pull Request — master (#56)
by
unknown
02:10
created

Serializer::resolveRealClassName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
109
                            $subClassMetadata = $this->getClassMetadataFromId($value[$identifierAttrKey]);
110
                            $value = $this->deserialize($value, $subClassMetadata->getModelName());
111
                        } else {
112
                            $list = [];
113
                            foreach ($value as $item) {
114
                                if (is_string($item)) {
115
                                    $list[] = $this->sdk->createProxy($item);
116
                                } elseif (is_array($item) && isset($item[$identifierAttrKey])) {
117
                                    // not tested for now
118
                                    // /the $identifierAttrKey is not the real identifier, as it is
119
                                    // the main object identifier, but we do not have the metadada for now
120
                                    // the thing we assume now is that every entity "may" have the same key
121
                                    // as identifier
122
                                    $key = $this->mapping->getKeyFromId($item[$identifierAttrKey]);
0 ignored issues
show
Unused Code introduced by
$key is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

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