Passed
Push — master ( 7067f6...c51298 )
by Julien
01:15 queued 11s
created

Serializer::resolveRealClassName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 2
dl 0
loc 13
ccs 5
cts 5
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Mapado\RestClientSdk\Model;
4
5
use libphonenumber\PhoneNumberFormat;
6
use libphonenumber\PhoneNumberUtil;
7
use Mapado\RestClientSdk\Exception\SdkException;
8
use Mapado\RestClientSdk\Helper\ArrayHelper;
9
use Mapado\RestClientSdk\Mapping;
10
use Mapado\RestClientSdk\Mapping\ClassMetadata;
11
use Mapado\RestClientSdk\SdkClient;
12
use Mapado\RestClientSdk\UnitOfWork;
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
    /**
34
     * @var UnitOfWork
35
     */
36
    private $unitOfWork;
37
38
    /**
39
     * Constructor.
40
     *
41
     * @param Mapping $mapping
42
     * @access public
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
     * @access public
55
     * @return Serializer
56
     */
57
    public function setSdk(SdkClient $sdk)
58
    {
59 1
        $this->sdk = $sdk;
60 1
        return $this;
61
    }
62
63
    /**
64
     * serialize entity for POST and PUT
65
     *
66
     * @param object $entity
67
     * @param string $modelName
68
     * @param array  $context
69
     * @access public
70
     * @return array
71
     */
72
    public function serialize($entity, $modelName, $context = [])
73
    {
74 1
        return $this->recursiveSerialize($entity, $modelName, 0, $context);
75
    }
76
77
    /**
78
     * deserialize
79
     *
80
     * @param array  $data
81
     * @param string $className
82
     * @access public
83
     * @return object
84
     */
85
    public function deserialize(array $data, $className)
86
    {
87 1
        $className = $this->resolveRealClassName($data, $className);
88
89 1
        $classMetadata = $this->mapping->getClassMetadata($className);
90 1
        $identifierAttribute = $classMetadata->getIdentifierAttribute();
91 1
        $identifierAttrKey = $identifierAttribute ? $identifierAttribute->getSerializedKey() : null;
0 ignored issues
show
introduced by
$identifierAttribute is of type Mapado\RestClientSdk\Mapping\Attribute, thus it always evaluated to true.
Loading history...
92
93 1
        $attributeList = $classMetadata->getAttributeList();
94
95 1
        $instance = new $className();
96
97 1
        foreach ($attributeList as $attribute) {
98 1
            $key = $attribute->getSerializedKey();
99
100 1
            if (!ArrayHelper::arrayHas($data, $key)) {
101 1
                continue;
102
            }
103
104 1
            $value = ArrayHelper::arrayGet($data, $key);
105
106 1
            $setter = 'set' . ucfirst($attribute->getAttributeName());
107
108 1
            if (method_exists($instance, $setter)) {
109 1
                $relation = $classMetadata->getRelation($key);
110 1
                if ($relation) {
111 1
                    if (is_string($value)) {
112 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

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