Completed
Push — master ( cc21f4...3eeaf5 )
by Julien
06:41 queued 03:06
created

Serializer::serialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
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
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
     * @required
54
     *
55
     * @param SdkClient $sdk
56
     *
57
     * @return Serializer
58
     */
59
    public function setSdk(SdkClient $sdk)
60
    {
61 1
        $this->sdk = $sdk;
62
63 1
        return $this;
64
    }
65
66
    /**
67
     * serialize entity for POST and PUT
68
     *
69
     * @param object $entity
70
     * @param string $modelName
71
     * @param array  $context
72
     *
73
     * @return array
74
     */
75
    public function serialize($entity, $modelName, $context = [])
76
    {
77 1
        return $this->recursiveSerialize($entity, $modelName, 0, $context);
78
    }
79
80
    /**
81
     * deserialize
82
     *
83
     * @param array  $data
84
     * @param string $className
85
     *
86
     * @return object
87
     */
88
    public function deserialize(array $data, $className)
89
    {
90 1
        $className = $this->resolveRealClassName($data, $className);
91
92 1
        $classMetadata = $this->mapping->getClassMetadata($className);
93
94 1
        $attributeList = $classMetadata->getAttributeList();
95
96 1
        $instance = new $className();
97
98 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...
99 1
            foreach ($attributeList as $attribute) {
100 1
                $key = $attribute->getSerializedKey();
101
102 1
                if (!ArrayHelper::arrayHas($data, $key)) {
103 1
                    continue;
104
                }
105
106 1
                $value = ArrayHelper::arrayGet($data, $key);
107
108 1
                $setter = 'set' . ucfirst($attribute->getAttributeName());
109
110 1
                if (method_exists($instance, $setter)) {
111 1
                    $relation = $classMetadata->getRelation($key);
112 1
                    if ($relation) {
113 1
                        if (is_string($value)) {
114 1
                            $value = $this->sdk->createProxy($value);
115 1
                        } elseif (is_array($value)) {
116 1
                            $targetEntity = $relation->getTargetEntity();
117 1
                            $relationClassMetadata = $this->mapping->getClassMetadata(
118 1
                                $targetEntity
119
                            );
120
121 1
                            if ($relation->isManyToOne()) {
122 1
                                $value = $this->deserialize(
123 1
                                    $value,
124 1
                                    $relationClassMetadata->getModelName()
125
                                );
126
                            } else {
127
                                // One-To-Many association
128 1
                                $list = [];
129 1
                                foreach ($value as $item) {
130 1
                                    if (is_string($item)) {
131
                                        $list[] = $this->sdk->createProxy(
132
                                            $item
133
                                        );
134 1
                                    } elseif (is_array($item)) {
135 1
                                        $list[] = $this->deserialize(
136 1
                                            $item,
137 1
                                            $relationClassMetadata->getModelName()
138
                                        );
139
                                    }
140
                                }
141
142 1
                                $value = $list;
143
                            }
144
                        }
145
                    }
146
147 1
                    if (isset($value)) {
148 1
                        if ('datetime' === $attribute->getType()) {
149 1
                            $value = new \DateTime($value);
150
                        }
151
152 1
                        $instance->{$setter}($value);
153
                    }
154
                }
155
            }
156
        }
157
158 1
        $classMetadata = $this->getClassMetadata($instance);
159 1
        if ($classMetadata->hasIdentifierAttribute()) {
160 1
            $idGetter = $classMetadata->getIdGetter();
161
162 1
            if ($idGetter) {
163 1
                $callable = [$instance, $idGetter];
164 1
                $identifier = is_callable($callable)
165 1
                    ? call_user_func($callable)
166 1
                    : null;
167
168 1
                if ($identifier) {
169 1
                    $this->unitOfWork->registerClean($identifier, $instance);
170
                }
171
            }
172
        }
173
174 1
        return $instance;
175
    }
176
177
    /**
178
     * If provided class name is abstract (a base class), the real class name (child class)
179
     * may be available in some data fields.
180
     *
181
     * @param array  $data
182
     * @param string $className
183
     *
184
     * @return string
185
     */
186
    private function resolveRealClassName(array $data, $className)
187
    {
188 1
        if (!empty($data['@id'])) {
189 1
            $classMetadata = $this->mapping->tryGetClassMetadataById(
190 1
                $data['@id']
191
            );
192
193 1
            if ($classMetadata) {
194 1
                return $classMetadata->getModelName();
195
            }
196
        }
197
198
        // Real class name could also be retrieved from @type property.
199 1
        return $className;
200
    }
201
202
    /**
203
     * recursiveSerialize
204
     *
205
     * @param object $entity
206
     * @param string $modelName
207
     * @param int    $level
208
     * @param array  $context
209
     *
210
     * @return array|mixed
211
     */
212
    private function recursiveSerialize(
213
        $entity,
214
        $modelName,
215
        $level = 0,
216
        $context = []
217
    ) {
218 1
        $classMetadata = $this->mapping->getClassMetadata($modelName);
219
220 1
        if ($level > 0 && empty($context['serializeRelation'])) {
221 1
            if ($classMetadata->hasIdentifierAttribute()) {
222 1
                $idAttribute = $classMetadata->getIdentifierAttribute();
223 1
                $getter = 'get' . ucfirst($idAttribute->getAttributeName());
224 1
                $tmpId = $entity->{$getter}();
225 1
                if ($tmpId) {
226 1
                    return $tmpId;
227
                }
228
            }
229
        }
230
231 1
        $attributeList = $classMetadata->getAttributeList();
232
233 1
        $out = [];
234 1
        if (!empty($attributeList)) {
235 1
            foreach ($attributeList as $attribute) {
236 1
                $method = 'get' . ucfirst($attribute->getAttributeName());
237
238 1
                if ($attribute->isIdentifier() && !$entity->{$method}()) {
239 1
                    continue;
240
                }
241 1
                $relation = $classMetadata->getRelation(
242 1
                    $attribute->getSerializedKey()
243
                );
244
245 1
                $data = $entity->{$method}();
246
247
                if (
248 1
                    null === $data &&
249 1
                    $relation &&
250 1
                    $relation->isManyToOne() &&
251 1
                    $level > 0
252
                ) {
253
                    /*
254
                        We only serialize the root many-to-one relations to prevent, hopefully,
255
                        unlinked and/or duplicated content. For instance, a cart with cartItemList containing
256
                        null values for the cart [{ cart => null, ... }] may lead the creation of
257
                        CartItem entities explicitly bound to a null Cart instead of the created/updated Cart.
258
                     */
259 1
                    continue;
260 1
                } elseif ($data instanceof \DateTime) {
261 1
                    $data = $data->format('c');
262 1
                } elseif (is_object($data) && $data instanceof PhoneNumber) {
263 1
                    $phoneNumberUtil = PhoneNumberUtil::getInstance();
264 1
                    $data = $phoneNumberUtil->format(
265 1
                        $data,
266 1
                        PhoneNumberFormat::INTERNATIONAL
267
                    );
268
                } elseif (
269 1
                    is_object($data) &&
270 1
                    $relation &&
271 1
                    $this->mapping->hasClassMetadata(
272 1
                        $relation->getTargetEntity()
273
                    )
274
                ) {
275 1
                    $relationClassMetadata = $this->mapping->getClassMetadata(
276 1
                        $relation->getTargetEntity()
277
                    );
278
279 1
                    if (!$relationClassMetadata->hasIdentifierAttribute()) {
280 1
                        $data = $this->recursiveSerialize(
281 1
                            $data,
282 1
                            $relation->getTargetEntity(),
283 1
                            $level + 1,
284 1
                            $context
285
                        );
286
                    } else {
287 1
                        $idAttribute = $relationClassMetadata->getIdentifierAttribute();
288
                        $idGetter =
289 1
                            'get' . ucfirst($idAttribute->getAttributeName());
290
291
                        if (
292 1
                            method_exists($data, $idGetter) &&
293 1
                            $data->{$idGetter}()
294
                        ) {
295 1
                            $data = $data->{$idGetter}();
296 1
                        } elseif ($relation->isManyToOne()) {
297 1
                            if ($level > 0) {
298 1
                                continue;
299
                            } else {
300 1
                                throw new SdkException(
301 1
                                    'Case not allowed for now'
302
                                );
303
                            }
304
                        }
305
                    }
306 1
                } elseif (is_array($data)) {
307 1
                    $newData = [];
308 1
                    foreach ($data as $key => $item) {
309 1
                        if ($item instanceof \DateTime) {
310 1
                            $newData[$key] = $item->format('c');
311
                        } elseif (
312 1
                            is_object($item) &&
313 1
                            $relation &&
314 1
                            $this->mapping->hasClassMetadata(
315 1
                                $relation->getTargetEntity()
316
                            )
317
                        ) {
318
                            $serializeRelation =
319 1
                                !empty($context['serializeRelations']) &&
320 1
                                in_array(
321 1
                                    $relation->getSerializedKey(),
322 1
                                    $context['serializeRelations']
323
                                );
324
325 1
                            $newData[$key] = $this->recursiveSerialize(
326 1
                                $item,
327 1
                                $relation->getTargetEntity(),
328 1
                                $level + 1,
329 1
                                ['serializeRelation' => $serializeRelation]
330
                            );
331
                        } else {
332 1
                            $newData[$key] = $item;
333
                        }
334
                    }
335 1
                    $data = $newData;
336
                }
337
338 1
                $key = $attribute->getSerializedKey();
339
340 1
                $out[$key] = $data;
341
            }
342
        }
343
344 1
        return $out;
345
    }
346
347
    /**
348
     * getClassMetadataFromId
349
     *
350
     * @param string $id
351
     *
352
     * @return ClassMetadata|null
353
     */
354
    private function getClassMetadataFromId($id)
355
    {
356
        $key = $this->mapping->getKeyFromId($id);
357
        $classMetadata = $this->mapping->getClassMetadataByKey($key);
358
359
        return $classMetadata;
360
    }
361
362
    private function getClassMetadata($entity)
363
    {
364 1
        return $this->mapping->getClassMetadata(get_class($entity));
365
    }
366
}
367