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) { |
|
|
|
|
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
|
|
|
|
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.