Passed
Push — master ( 01c95e...1b44dd )
by Julien
01:08
created

Serializer::getClassMetadataFromId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
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($identifier, $instance);
158
                }
159
            }
160
        }
161
162 1
        return $instance;
163
    }
164
165
    /**
166
     * If provided class name is abstract (a base class), the real class name (child class)
167
     * may be available in some data fields.
168
     */
169
    private function resolveRealClassName(
170
        array $data,
171
        string $className
172
    ): string {
173 1
        if (!empty($data['@id'])) {
174 1
            $classMetadata = $this->mapping->tryGetClassMetadataById(
175 1
                $data['@id']
176
            );
177
178 1
            if ($classMetadata) {
179 1
                return $classMetadata->getModelName();
180
            }
181
        }
182
183
        // Real class name could also be retrieved from @type property.
184 1
        return $className;
185
    }
186
187
    /**
188
     * @return array|string
189
     */
190
    private function recursiveSerialize(
191
        object $entity,
192
        string $modelName,
193
        int $level = 0,
194
        array $context = []
195
    ) {
196 1
        $classMetadata = $this->mapping->getClassMetadata($modelName);
197
198 1
        if ($level > 0 && empty($context['serializeRelation'])) {
199 1
            if ($classMetadata->hasIdentifierAttribute()) {
200 1
                $tmpId = $entity->{$classMetadata->getIdGetter()}();
201 1
                if ($tmpId) {
202 1
                    return $tmpId;
203
                }
204
            }
205
        }
206
207 1
        $attributeList = $classMetadata->getAttributeList();
208
209 1
        $out = [];
210 1
        if (!empty($attributeList)) {
211 1
            foreach ($attributeList as $attribute) {
212 1
                $method = 'get' . ucfirst($attribute->getAttributeName());
213
214 1
                if ($attribute->isIdentifier() && !$entity->{$method}()) {
215 1
                    continue;
216
                }
217 1
                $relation = $classMetadata->getRelation(
218 1
                    $attribute->getSerializedKey()
219
                );
220
221 1
                $data = $entity->{$method}();
222
223
                if (
224 1
                    null === $data &&
225 1
                    $relation &&
226 1
                    $relation->isManyToOne() &&
227 1
                    $level > 0
228
                ) {
229
                    /*
230
                        We only serialize the root many-to-one relations to prevent, hopefully,
231
                        unlinked and/or duplicated content. For instance, a cart with cartItemList containing
232
                        null values for the cart [{ cart => null, ... }] may lead the creation of
233
                        CartItem entities explicitly bound to a null Cart instead of the created/updated Cart.
234
                     */
235 1
                    continue;
236 1
                } elseif ($data instanceof \DateTime) {
237 1
                    $data = $data->format('c');
238 1
                } elseif (is_object($data) && $data instanceof PhoneNumber) {
239 1
                    $phoneNumberUtil = PhoneNumberUtil::getInstance();
240 1
                    $data = $phoneNumberUtil->format(
241 1
                        $data,
242 1
                        PhoneNumberFormat::INTERNATIONAL
243
                    );
244
                } elseif (
245 1
                    is_object($data) &&
246 1
                    $relation &&
247 1
                    $this->mapping->hasClassMetadata(
248 1
                        $relation->getTargetEntity()
249
                    )
250
                ) {
251 1
                    $relationClassMetadata = $this->mapping->getClassMetadata(
252 1
                        $relation->getTargetEntity()
253
                    );
254
255 1
                    if (!$relationClassMetadata->hasIdentifierAttribute()) {
256 1
                        $data = $this->recursiveSerialize(
257 1
                            $data,
258 1
                            $relation->getTargetEntity(),
259 1
                            $level + 1,
260 1
                            $context
261
                        );
262
                    } else {
263 1
                        $idAttribute = $relationClassMetadata->getIdentifierAttribute();
264
                        $idGetter =
265 1
                            'get' . ucfirst($idAttribute->getAttributeName());
266
267
                        if (
268 1
                            method_exists($data, $idGetter) &&
269 1
                            $data->{$idGetter}()
270
                        ) {
271 1
                            $data = $data->{$idGetter}();
272 1
                        } elseif ($relation->isManyToOne()) {
273 1
                            if ($level > 0) {
274 1
                                continue;
275
                            } else {
276 1
                                throw new SdkException(
277 1
                                    'Case not allowed for now'
278
                                );
279
                            }
280
                        }
281
                    }
282 1
                } elseif (is_array($data)) {
283 1
                    $newData = [];
284 1
                    foreach ($data as $key => $item) {
285 1
                        if ($item instanceof \DateTime) {
286 1
                            $newData[$key] = $item->format('c');
287
                        } elseif (
288 1
                            is_object($item) &&
289 1
                            $relation &&
290 1
                            $this->mapping->hasClassMetadata(
291 1
                                $relation->getTargetEntity()
292
                            )
293
                        ) {
294
                            $serializeRelation =
295 1
                                !empty($context['serializeRelations']) &&
296 1
                                in_array(
297 1
                                    $relation->getSerializedKey(),
298 1
                                    $context['serializeRelations']
299
                                );
300
301 1
                            $newData[$key] = $this->recursiveSerialize(
302 1
                                $item,
303 1
                                $relation->getTargetEntity(),
304 1
                                $level + 1,
305 1
                                ['serializeRelation' => $serializeRelation]
306
                            );
307
                        } else {
308 1
                            $newData[$key] = $item;
309
                        }
310
                    }
311 1
                    $data = $newData;
312
                }
313
314 1
                $key = $attribute->getSerializedKey();
315
316 1
                $out[$key] = $data;
317
            }
318
        }
319
320 1
        return $out;
321
    }
322
323
    private function getClassMetadataFromId(string $id): ?ClassMetadata
324
    {
325
        $key = $this->mapping->getKeyFromId($id);
326
327
        return $this->mapping->getClassMetadataByKey($key);
328
    }
329
330
    private function getClassMetadata(object $entity): ClassMetadata
331
    {
332 1
        return $this->mapping->getClassMetadata(get_class($entity));
333
    }
334
}
335