Completed
Pull Request — master (#58)
by
unknown
06:38
created

Serializer::getClassMetadata()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
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 Mapado\RestClientSdk\UnitOfWork;
11
use libphonenumber\PhoneNumberFormat;
12
use libphonenumber\PhoneNumberUtil;
13
14
/**
15
 * Class Serializer
16
 * @author Julien Deniau <[email protected]>
17
 */
18
class Serializer
19
{
20
    /**
21
     * mapping
22
     *
23
     * @var Mapping
24
     * @access private
25
     */
26
    private $mapping;
27
28
    /**
29
     * @var SdkClient|null
30
     */
31
    private $sdk;
32
    /**
33
     * Constructor.
34
     *
35
     * @param Mapping $mapping
36
     * @access public
37
     */
38
    public function __construct(Mapping $mapping, UnitOfWork $unitOfWork)
39
    {
40
        $this->mapping = $mapping;
41
        $this->unitOfWork = $unitOfWork;
0 ignored issues
show
Bug introduced by
The property unitOfWork does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
42
    }
43
44
    /**
45
     * setSdk
46
     *
47
     * @param SdkClient $sdk
48
     * @access public
49
     * @return Serializer
50
     */
51
    public function setSdk(SdkClient $sdk)
52
    {
53
        $this->sdk = $sdk;
54
        return $this;
55
    }
56
57
    /**
58
     * serialize entity for POST and PUT
59
     *
60
     * @param object $entity
61
     * @param string $modelName
62
     * @param array  $context
63
     * @access public
64
     * @return array
65
     */
66
    public function serialize($entity, $modelName, $context = [])
67
    {
68
        return $this->recursiveSerialize($entity, $modelName, 0, $context);
69
    }
70
71
    /**
72
     * deserialize
73
     *
74
     * @param array  $data
75
     * @param string $className
76
     * @access public
77
     * @return object
78
     */
79
    public function deserialize(array $data, $className)
80
    {
81
        $className = $this->resolveRealClassName($data, $className);
82
83
        $classMetadata = $this->mapping->getClassMetadata($className);
84
        $identifierAttribute = $classMetadata->getIdentifierAttribute();
85
        $identifierAttrKey = $identifierAttribute ? $identifierAttribute->getSerializedKey() : null;
86
87
        $attributeList = $classMetadata->getAttributeList();
88
89
        $instance = new $className();
90
91
        foreach ($attributeList as $attribute) {
92
            $key = $attribute->getSerializedKey();
93
94
            if (!ArrayHelper::arrayHas($data, $key)) {
95
                continue;
96
            }
97
98
            $value = ArrayHelper::arrayGet($data, $key);
99
100
            $setter = 'set' . ucfirst($attribute->getAttributeName());
101
102
            if (method_exists($instance, $setter)) {
103
                $relation = $classMetadata->getRelation($key);
104
                if ($relation) {
105
                    if (is_string($value)) {
106
                        $value = $this->sdk->createProxy($value);
107
                    } elseif (is_array($value)) {
108
                        if (isset($value[$identifierAttrKey])) {
109
                            $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...
110
                            $subClassMetadata = $this->getClassMetadataFromId($value[$identifierAttrKey]);
111
                            $value = $this->deserialize($value, $subClassMetadata->getModelName());
112
                        } else {
113
                            $list = [];
114
                            foreach ($value as $item) {
115
                                if (is_string($item)) {
116
                                    $list[] = $this->sdk->createProxy($item);
117
                                } elseif (is_array($item) && isset($item[$identifierAttrKey])) {
118
                                    // not tested for now
119
                                    // /the $identifierAttrKey is not the real identifier, as it is
120
                                    // the main object identifier, but we do not have the metadada for now
121
                                    // the thing we assume now is that every entity "may" have the same key
122
                                    // as identifier
123
                                    $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...
124
                                    $subClassMetadata = $this->getClassMetadataFromId($item[$identifierAttrKey]);
125
                                    $list[] = $this->deserialize($item, $subClassMetadata->getModelName());
126
                                }
127
                            }
128
129
                            $value = $list;
130
                        }
131
                    }
132
                }
133
134
                if (isset($value)) {
135
                    if ($attribute && $attribute->getType() === 'datetime') {
136
                        $value = new \DateTime($value);
137
                    }
138
139
                    $instance->$setter($value);
140
                }
141
            }
142
        }
143
144
        $identifier = $instance->{$this->getClassMetadata($instance)->getIdGetter()}();
145
        if ($identifier) {
146
            $this->unitOfWork->registerClean($identifier, $instance);
147
        }
148
149
        return $instance;
150
    }
151
152
    /**
153
     * If provided class name is abstract (a base class), the real class name (child class)
154
     * may be available in some data fields.
155
     *
156
     * @param array  $data
157
     * @param string $className
158
     * @access private
159
     * @return string
160
     */
161
    private function resolveRealClassName(array $data, $className)
162
    {
163
        if (!empty($data['@id'])) {
164
            $classMetadata = $this->mapping->tryGetClassMetadataById($data['@id']);
165
166
            if ($classMetadata) {
167
                return $classMetadata->getModelName();
168
            }
169
        }
170
171
        // Real class name could also be retrieved from @type property.
172
173
        return $className;
174
    }
175
176
    /**
177
     * recursiveSerialize
178
     *
179
     * @param object $entity
180
     * @param string $modelName
181
     * @param int    $level
182
     * @param array  $context
183
     * @access private
184
     * @return array|mixed
185
     */
186
    private function recursiveSerialize($entity, $modelName, $level = 0, $context = [])
187
    {
188
        $classMetadata = $this->mapping->getClassMetadata($modelName);
189
190
        if ($level > 0 && empty($context['serializeRelation'])) {
191
            $idAttribute = $classMetadata->getIdentifierAttribute();
192
            $getter = 'get' . ucfirst($idAttribute->getAttributeName());
193
            $tmpId = $entity->{$getter}();
194
            if ($tmpId) {
195
                return $tmpId;
196
            }
197
        }
198
199
        $attributeList = $classMetadata->getAttributeList();
200
201
        $out = [];
202
        if (!empty($attributeList)) {
203
            foreach ($attributeList as $attribute) {
204
                $method = 'get' . ucfirst($attribute->getAttributeName());
205
206
                if ($attribute->isIdentifier() && !$entity->$method()) {
207
                    continue;
208
                }
209
                $relation = $classMetadata->getRelation($attribute->getSerializedKey());
210
211
                $data = $entity->$method();
212
213
                if (null === $data && $relation && $relation->isManyToOne() && $level > 0) {
214
                    /*
215
                        We only serialize the root many-to-one relations to prevent, hopefully,
216
                        unlinked and/or duplicated content. For instance, a cart with cartItemList containing
217
                        null values for the cart [{ cart => null, ... }] may lead the creation of
218
                        CartItem entities explicitly bound to a null Cart instead of the created/updated Cart.
219
                     */
220
                    continue;
221
                } elseif ($data instanceof \DateTime) {
222
                    $data = $data->format('c');
223
                } elseif (is_object($data) && get_class($data) === "libphonenumber\PhoneNumber") {
224
                    $phoneNumberUtil = PhoneNumberUtil::getInstance();
225
                    $data = $phoneNumberUtil->format(
226
                        $data,
227
                        PhoneNumberFormat::INTERNATIONAL
228
                    );
229
                } elseif (is_object($data)
230
                    && $relation
231
                    && $this->mapping->hasClassMetadata($relation->getTargetEntity())
232
                ) {
233
                    $idAttribute = $this->mapping
234
                        ->getClassMetadata($relation->getTargetEntity())
235
                        ->getIdentifierAttribute()
236
                        ->getAttributeName();
237
                    $idGetter = 'get' . ucfirst($idAttribute);
238
239
                    if (method_exists($data, $idGetter) && $data->{$idGetter}()) {
240
                        $data = $data->{$idGetter}();
241
                    } elseif ($relation->isManyToOne()) {
242
                        if ($level > 0) {
243
                            continue;
244
                        } else {
245
                            throw new SdkException('Case not allowed for now');
246
                        }
247
                    }
248
                } elseif (is_array($data)) {
249
                    $newData = [];
250
                    foreach ($data as $key => $item) {
251
                        if ($item instanceof \DateTime) {
252
                            $newData[$key] = $item->format('c');
253
                        } elseif (is_object($item) &&
254
                            $relation &&
255
                            $this->mapping->hasClassMetadata($relation->getTargetEntity())
256
                        ) {
257
                            $serializeRelation = !empty($context['serializeRelations'])
258
                                && in_array($relation->getSerializedKey(), $context['serializeRelations']);
259
260
                            $newData[$key] = $this->recursiveSerialize(
261
                                $item,
262
                                $relation->getTargetEntity(),
263
                                $level + 1,
264
                                [ 'serializeRelation' => $serializeRelation ]
265
                            );
266
                        } else {
267
                            $newData[$key] = $item;
268
                        }
269
                    }
270
                    $data = $newData;
271
                }
272
273
                $key = $attribute->getSerializedKey();
274
275
                $out[$key] = $data;
276
            }
277
        }
278
279
        return $out;
280
    }
281
282
    /**
283
     * getClassMetadataFromId
284
     *
285
     * @param string $id
286
     * @access private
287
     * @return ClassMetadata|null
288
     */
289
    private function getClassMetadataFromId($id)
290
    {
291
        $key = $this->mapping->getKeyFromId($id);
292
        $classMetadata = $this->mapping->getClassMetadataByKey($key);
293
        return $classMetadata;
294
    }
295
296
    private function getClassMetadata($entity)
297
    {
298
        return $this->mapping
299
            ->getClassMetadata(get_class($entity));
300
    }
301
}
302