Passed
Push — master ( 73a0c9...8dd79d )
by Julien
59s queued 10s
created

Serializer::getClassMetadata()   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 1
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|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
Unused Code introduced by
The assignment to $identifierAttrKey is dead and can be removed.
Loading history...
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
                        $targetEntity = $relation->getTargetEntity();
118 1
                        $relationClassMetadata = $this->mapping->getClassMetadata(
119 1
                            $targetEntity
120
                        );
121
122 1
                        $relationIdentifierAttribute = $relationClassMetadata->getIdentifierAttribute();
123 1
                        $relationIdentifierAttrKey = $relationIdentifierAttribute
0 ignored issues
show
Unused Code introduced by
The assignment to $relationIdentifierAttrKey is dead and can be removed.
Loading history...
124 1
                            ? $relationIdentifierAttribute->getSerializedKey()
125 1
                            : null;
126
127 1
                        if ($relation->isManyToOne()) {
128 1
                            $value = $this->deserialize(
129 1
                                $value,
130 1
                                $relationClassMetadata->getModelName()
131
                            );
132
                        } else {
133
                            // One-To-Many association
134 1
                            $list = [];
135 1
                            foreach ($value as $item) {
136 1
                                if (is_string($item)) {
137
                                    $list[] = $this->sdk->createProxy($item);
138 1
                                } elseif (is_array($item)) {
139 1
                                    $list[] = $this->deserialize(
140 1
                                        $item,
141 1
                                        $relationClassMetadata->getModelName()
142
                                    );
143
                                }
144
                            }
145
146 1
                            $value = $list;
147
                        }
148
                    }
149
                }
150
151 1
                if (isset($value)) {
152 1
                    if ($attribute && 'datetime' === $attribute->getType()) {
153 1
                        $value = new \DateTime($value);
154
                    }
155
156 1
                    $instance->{$setter}($value);
157
                }
158
            }
159
        }
160
161 1
        $idGetter = $this->getClassMetadata($instance)->getIdGetter();
162
163 1
        if ($idGetter) {
164 1
            $callable = [$instance, $idGetter];
165 1
            $identifier = is_callable($callable)
166 1
                ? call_user_func($callable)
167 1
                : null;
168
169 1
            if ($identifier) {
170 1
                $this->unitOfWork->registerClean($identifier, $instance);
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
            $idAttribute = $classMetadata->getIdentifierAttribute();
222 1
            if ($idAttribute) {
0 ignored issues
show
introduced by
$idAttribute is of type Mapado\RestClientSdk\Mapping\Attribute, thus it always evaluated to true.
Loading history...
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
                    $idAttribute = $this->mapping->getClassMetadata(
276 1
                        $relation->getTargetEntity()
277 1
                    )->getIdentifierAttribute();
278
279 1
                    if (!$idAttribute) {
280 1
                        $data = $this->recursiveSerialize(
281 1
                            $data,
282 1
                            $relation->getTargetEntity(),
283 1
                            $level + 1,
284 1
                            $context
285
                        );
286
                    } else {
287
                        $idGetter =
288 1
                            'get' . ucfirst($idAttribute->getAttributeName());
289
290
                        if (
291 1
                            method_exists($data, $idGetter) &&
292 1
                            $data->{$idGetter}()
293
                        ) {
294 1
                            $data = $data->{$idGetter}();
295 1
                        } elseif ($relation->isManyToOne()) {
296 1
                            if ($level > 0) {
297 1
                                continue;
298
                            } else {
299 1
                                throw new SdkException(
300 1
                                    'Case not allowed for now'
301
                                );
302
                            }
303
                        }
304
                    }
305 1
                } elseif (is_array($data)) {
306 1
                    $newData = [];
307 1
                    foreach ($data as $key => $item) {
308 1
                        if ($item instanceof \DateTime) {
309 1
                            $newData[$key] = $item->format('c');
310
                        } elseif (
311 1
                            is_object($item) &&
312 1
                            $relation &&
313 1
                            $this->mapping->hasClassMetadata(
314 1
                                $relation->getTargetEntity()
315
                            )
316
                        ) {
317
                            $serializeRelation =
318 1
                                !empty($context['serializeRelations']) &&
319 1
                                in_array(
320 1
                                    $relation->getSerializedKey(),
321 1
                                    $context['serializeRelations']
322
                                );
323
324 1
                            $newData[$key] = $this->recursiveSerialize(
325 1
                                $item,
326 1
                                $relation->getTargetEntity(),
327 1
                                $level + 1,
328 1
                                ['serializeRelation' => $serializeRelation]
329
                            );
330
                        } else {
331 1
                            $newData[$key] = $item;
332
                        }
333
                    }
334 1
                    $data = $newData;
335
                }
336
337 1
                $key = $attribute->getSerializedKey();
338
339 1
                $out[$key] = $data;
340
            }
341
        }
342
343 1
        return $out;
344
    }
345
346
    /**
347
     * getClassMetadataFromId
348
     *
349
     * @param string $id
350
     *
351
     * @return ClassMetadata|null
352
     */
353
    private function getClassMetadataFromId($id)
354
    {
355
        $key = $this->mapping->getKeyFromId($id);
356
        $classMetadata = $this->mapping->getClassMetadataByKey($key);
357
358
        return $classMetadata;
359
    }
360
361
    private function getClassMetadata($entity)
362
    {
363 1
        return $this->mapping->getClassMetadata(get_class($entity));
364
    }
365
}
366