1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace JMS\Serializer\Metadata; |
6
|
|
|
|
7
|
|
|
use JMS\Serializer\Exception\InvalidMetadataException; |
8
|
|
|
use JMS\Serializer\Expression\Expression; |
9
|
|
|
use JMS\Serializer\Ordering\AlphabeticalPropertyOrderingStrategy; |
10
|
|
|
use JMS\Serializer\Ordering\CustomPropertyOrderingStrategy; |
11
|
|
|
use JMS\Serializer\Ordering\IdenticalPropertyOrderingStrategy; |
12
|
|
|
use Metadata\MergeableClassMetadata; |
13
|
|
|
use Metadata\MergeableInterface; |
14
|
|
|
use Metadata\MethodMetadata; |
15
|
|
|
use Metadata\PropertyMetadata as BasePropertyMetadata; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Class Metadata used to customize the serialization process. |
19
|
|
|
* |
20
|
|
|
* @author Johannes M. Schmitt <[email protected]> |
21
|
|
|
* |
22
|
|
|
* @property PropertyMetadata[] $propertyMetadata |
23
|
|
|
*/ |
24
|
|
|
class ClassMetadata extends MergeableClassMetadata |
25
|
|
|
{ |
26
|
|
|
public const ACCESSOR_ORDER_UNDEFINED = 'undefined'; |
27
|
|
|
public const ACCESSOR_ORDER_ALPHABETICAL = 'alphabetical'; |
28
|
|
|
public const ACCESSOR_ORDER_CUSTOM = 'custom'; |
29
|
|
|
|
30
|
|
|
/** @var \ReflectionMethod[] */ |
31
|
|
|
public $preSerializeMethods = []; |
32
|
|
|
|
33
|
|
|
/** @var \ReflectionMethod[] */ |
34
|
|
|
public $postSerializeMethods = []; |
35
|
|
|
|
36
|
|
|
/** @var \ReflectionMethod[] */ |
37
|
|
|
public $postDeserializeMethods = []; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var string |
41
|
|
|
*/ |
42
|
|
|
public $xmlRootName; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var string |
46
|
|
|
*/ |
47
|
|
|
public $xmlRootNamespace; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var string |
51
|
|
|
*/ |
52
|
|
|
public $xmlRootPrefix; |
53
|
|
|
/** |
54
|
|
|
* @var string[] |
55
|
|
|
*/ |
56
|
|
|
public $xmlNamespaces = []; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var string |
60
|
|
|
*/ |
61
|
|
|
public $accessorOrder; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @var string[] |
65
|
|
|
*/ |
66
|
|
|
public $customOrder; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @internal |
70
|
|
|
* |
71
|
|
|
* @var bool |
72
|
32 |
|
*/ |
73
|
|
|
public $usingExpression = false; |
74
|
32 |
|
|
75
|
|
|
/** |
76
|
|
|
* @internal |
77
|
|
|
* |
78
|
32 |
|
* @var bool |
79
|
|
|
*/ |
80
|
|
|
public $isList = false; |
81
|
|
|
|
82
|
32 |
|
/** |
83
|
32 |
|
* @internal |
84
|
32 |
|
* |
85
|
32 |
|
* @var bool |
86
|
32 |
|
*/ |
87
|
|
|
public $isMap = false; |
88
|
15 |
|
|
89
|
|
|
/** |
90
|
15 |
|
* @var bool |
91
|
|
|
*/ |
92
|
|
|
public $discriminatorDisabled = false; |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* @var string |
96
|
|
|
*/ |
97
|
|
|
public $discriminatorBaseClass; |
98
|
|
|
/** |
99
|
|
|
* @var string |
100
|
|
|
*/ |
101
|
|
|
public $discriminatorFieldName; |
102
|
39 |
|
/** |
103
|
|
|
* @var string |
104
|
39 |
|
*/ |
105
|
|
|
public $discriminatorValue; |
106
|
|
|
|
107
|
|
|
/** |
108
|
39 |
|
* @var string[] |
109
|
34 |
|
*/ |
110
|
34 |
|
public $discriminatorMap = []; |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @var string[] |
114
|
39 |
|
*/ |
115
|
39 |
|
public $discriminatorGroups = []; |
116
|
39 |
|
|
117
|
39 |
|
/** |
118
|
|
|
* @var bool |
119
|
282 |
|
*/ |
120
|
|
|
public $xmlDiscriminatorAttribute = false; |
121
|
282 |
|
|
122
|
282 |
|
/** |
123
|
282 |
|
* @var bool |
124
|
26 |
|
*/ |
125
|
|
|
public $xmlDiscriminatorCData = true; |
126
|
282 |
|
|
127
|
|
|
/** |
128
|
2 |
|
* @var string |
129
|
|
|
*/ |
130
|
2 |
|
public $xmlDiscriminatorNamespace; |
131
|
2 |
|
|
132
|
|
|
/** |
133
|
2 |
|
* @var string|Expression |
134
|
|
|
*/ |
135
|
2 |
|
public $excludeIf; |
136
|
2 |
|
|
137
|
|
|
public function setDiscriminator(string $fieldName, array $map, array $groups = []): void |
138
|
4 |
|
{ |
139
|
|
|
if (empty($fieldName)) { |
140
|
4 |
|
throw new InvalidMetadataException('The $fieldName cannot be empty.'); |
141
|
4 |
|
} |
142
|
|
|
|
143
|
27 |
|
if (empty($map)) { |
144
|
|
|
throw new InvalidMetadataException('The discriminator map cannot be empty.'); |
145
|
27 |
|
} |
146
|
|
|
|
147
|
|
|
$this->discriminatorBaseClass = $this->name; |
148
|
27 |
|
$this->discriminatorFieldName = $fieldName; |
149
|
|
|
$this->discriminatorMap = $map; |
150
|
27 |
|
$this->discriminatorGroups = $groups; |
151
|
27 |
|
|
152
|
27 |
|
$this->handleDiscriminatorProperty(); |
153
|
27 |
|
} |
154
|
27 |
|
|
155
|
27 |
|
private function getReflection(): \ReflectionClass |
156
|
|
|
{ |
157
|
27 |
|
return new \ReflectionClass($this->name); |
158
|
2 |
|
} |
159
|
2 |
|
|
160
|
|
|
/** |
161
|
|
|
* Sets the order of properties in the class. |
162
|
27 |
|
* |
163
|
|
|
* @param array $customOrder |
164
|
|
|
* |
165
|
|
|
* @throws InvalidMetadataException When the accessor order is not valid. |
166
|
|
|
* @throws InvalidMetadataException When the custom order is not valid. |
167
|
|
|
*/ |
168
|
|
|
public function setAccessorOrder(string $order, array $customOrder = []): void |
169
|
27 |
|
{ |
170
|
2 |
|
if (!in_array($order, [self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM], true)) { |
171
|
2 |
|
throw new InvalidMetadataException(sprintf('The accessor order "%s" is invalid.', $order)); |
172
|
|
|
} |
173
|
|
|
|
174
|
27 |
|
foreach ($customOrder as $name) { |
175
|
27 |
|
if (!\is_string($name)) { |
176
|
|
|
throw new InvalidMetadataException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name))); |
177
|
|
|
} |
178
|
27 |
|
} |
179
|
2 |
|
|
180
|
2 |
|
$this->accessorOrder = $order; |
181
|
2 |
|
$this->customOrder = array_flip($customOrder); |
182
|
|
|
$this->sortProperties(); |
183
|
|
|
} |
184
|
27 |
|
|
185
|
15 |
|
public function addPropertyMetadata(BasePropertyMetadata $metadata): void |
186
|
|
|
{ |
187
|
|
|
parent::addPropertyMetadata($metadata); |
188
|
|
|
|
189
|
|
|
$this->sortProperties(); |
190
|
|
|
if ($metadata instanceof PropertyMetadata && $metadata->excludeIf) { |
191
|
|
|
$this->usingExpression = true; |
192
|
|
|
} |
193
|
15 |
|
} |
194
|
|
|
|
195
|
15 |
|
public function addPreSerializeMethod(MethodMetadata $method): void |
196
|
15 |
|
{ |
197
|
|
|
$this->preSerializeMethods[] = $method; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
public function addPostSerializeMethod(MethodMetadata $method): void |
201
|
|
|
{ |
202
|
|
|
$this->postSerializeMethods[] = $method; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
public function addPostDeserializeMethod(MethodMetadata $method): void |
206
|
15 |
|
{ |
207
|
15 |
|
$this->postDeserializeMethods[] = $method; |
208
|
15 |
|
} |
209
|
15 |
|
|
210
|
15 |
|
public function merge(MergeableInterface $object): void |
211
|
|
|
{ |
212
|
15 |
|
if (!$object instanceof ClassMetadata) { |
213
|
15 |
|
throw new InvalidMetadataException('$object must be an instance of ClassMetadata.'); |
214
|
15 |
|
} |
215
|
15 |
|
|
216
|
15 |
|
parent::merge($object); |
217
|
|
|
|
218
|
|
|
$this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods); |
219
|
27 |
|
$this->postSerializeMethods = array_merge($this->postSerializeMethods, $object->postSerializeMethods); |
220
|
27 |
|
$this->postDeserializeMethods = array_merge($this->postDeserializeMethods, $object->postDeserializeMethods); |
221
|
|
|
$this->xmlRootName = $object->xmlRootName; |
222
|
31 |
|
$this->xmlRootPrefix = $object->xmlRootPrefix; |
223
|
|
|
$this->xmlRootNamespace = $object->xmlRootNamespace; |
224
|
31 |
|
if (null !== $object->excludeIf) { |
225
|
|
|
$this->excludeIf = $object->excludeIf; |
226
|
|
|
} |
227
|
|
|
|
228
|
31 |
|
$this->isMap = $object->isMap; |
229
|
31 |
|
$this->isList = $object->isList; |
230
|
31 |
|
|
231
|
|
|
$this->xmlNamespaces = array_merge($this->xmlNamespaces, $object->xmlNamespaces); |
232
|
|
|
|
233
|
2 |
|
if ($object->accessorOrder) { |
234
|
|
|
$this->accessorOrder = $object->accessorOrder; |
235
|
|
|
$this->customOrder = $object->customOrder; |
236
|
31 |
|
} |
237
|
31 |
|
|
238
|
|
|
if ($object->discriminatorFieldName && $this->discriminatorFieldName && $object->discriminatorFieldName !== $this->discriminatorFieldName) { |
239
|
|
|
throw new InvalidMetadataException(sprintf( |
240
|
|
|
'The discriminator of class "%s" would overwrite the discriminator of the parent class "%s". Please define all possible sub-classes in the discriminator of %s.', |
241
|
|
|
$object->name, |
242
|
|
|
$this->discriminatorBaseClass, |
243
|
|
|
$this->discriminatorBaseClass, |
244
|
|
|
)); |
245
|
|
|
} elseif (!$this->discriminatorFieldName && $object->discriminatorFieldName) { |
246
|
|
|
$this->discriminatorFieldName = $object->discriminatorFieldName; |
247
|
|
|
$this->discriminatorMap = $object->discriminatorMap; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
if (null !== $object->discriminatorDisabled) { |
251
|
|
|
$this->discriminatorDisabled = $object->discriminatorDisabled; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
if ($object->discriminatorMap) { |
|
|
|
|
255
|
|
|
$this->discriminatorFieldName = $object->discriminatorFieldName; |
256
|
|
|
$this->discriminatorMap = $object->discriminatorMap; |
257
|
|
|
$this->discriminatorBaseClass = $object->discriminatorBaseClass; |
258
|
|
|
$this->discriminatorGroups = $object->discriminatorGroups; |
259
|
|
|
|
260
|
|
|
$this->xmlDiscriminatorCData = $object->xmlDiscriminatorCData; |
261
|
|
|
$this->xmlDiscriminatorAttribute = $object->xmlDiscriminatorAttribute; |
262
|
|
|
$this->xmlDiscriminatorNamespace = $object->xmlDiscriminatorNamespace; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
$this->handleDiscriminatorProperty(); |
266
|
|
|
|
267
|
|
|
$this->sortProperties(); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
public function registerNamespace(string $uri, ?string $prefix = null): void |
271
|
|
|
{ |
272
|
|
|
if (!\is_string($uri)) { |
|
|
|
|
273
|
|
|
throw new InvalidMetadataException(sprintf('$uri is expected to be a strings, but got value %s.', json_encode($uri))); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
if (null !== $prefix) { |
277
|
|
|
if (!\is_string($prefix)) { |
|
|
|
|
278
|
|
|
throw new InvalidMetadataException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix))); |
279
|
|
|
} |
280
|
|
|
} else { |
281
|
|
|
$prefix = ''; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
$this->xmlNamespaces[$prefix] = $uri; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
protected function serializeToArray(): array |
288
|
|
|
{ |
289
|
|
|
$this->sortProperties(); |
290
|
|
|
|
291
|
|
|
return [ |
292
|
|
|
$this->preSerializeMethods, |
293
|
|
|
$this->postSerializeMethods, |
294
|
|
|
$this->postDeserializeMethods, |
295
|
|
|
$this->xmlRootName, |
296
|
|
|
$this->xmlRootNamespace, |
297
|
|
|
$this->xmlNamespaces, |
298
|
|
|
$this->accessorOrder, |
299
|
|
|
$this->customOrder, |
300
|
|
|
$this->discriminatorDisabled, |
301
|
|
|
$this->discriminatorBaseClass, |
302
|
|
|
$this->discriminatorFieldName, |
303
|
|
|
$this->discriminatorValue, |
304
|
|
|
$this->discriminatorMap, |
305
|
|
|
$this->discriminatorGroups, |
306
|
|
|
$this->excludeIf, |
307
|
|
|
$this->discriminatorGroups, |
308
|
|
|
$this->xmlDiscriminatorAttribute, |
309
|
|
|
$this->xmlDiscriminatorCData, |
310
|
|
|
$this->usingExpression, |
311
|
|
|
$this->xmlDiscriminatorNamespace, |
312
|
|
|
$this->xmlRootPrefix, |
313
|
|
|
$this->isList, |
314
|
|
|
$this->isMap, |
315
|
|
|
parent::serializeToArray(), |
316
|
|
|
]; |
317
|
|
|
} |
318
|
|
|
|
319
|
286 |
|
protected function unserializeFromArray(array $data): void |
320
|
|
|
{ |
321
|
286 |
|
[ |
322
|
286 |
|
$this->preSerializeMethods, |
323
|
|
|
$this->postSerializeMethods, |
324
|
|
|
$this->postDeserializeMethods, |
325
|
|
|
$this->xmlRootName, |
326
|
286 |
|
$this->xmlRootNamespace, |
327
|
7 |
|
$this->xmlNamespaces, |
328
|
7 |
|
$this->accessorOrder, |
329
|
|
|
$this->customOrder, |
330
|
284 |
|
$this->discriminatorDisabled, |
331
|
34 |
|
$this->discriminatorBaseClass, |
332
|
34 |
|
$this->discriminatorFieldName, |
333
|
|
|
$this->discriminatorValue, |
334
|
286 |
|
$this->discriminatorMap, |
335
|
|
|
$this->discriminatorGroups, |
336
|
|
|
$this->excludeIf, |
337
|
|
|
$this->discriminatorGroups, |
338
|
|
|
$this->xmlDiscriminatorAttribute, |
339
|
|
|
$this->xmlDiscriminatorCData, |
340
|
|
|
$this->usingExpression, |
341
|
|
|
$this->xmlDiscriminatorNamespace, |
342
|
|
|
$this->xmlRootPrefix, |
343
|
|
|
$this->isList, |
344
|
|
|
$this->isMap, |
345
|
|
|
$parentData, |
346
|
|
|
] = $data; |
347
|
|
|
|
348
|
|
|
parent::unserializeFromArray($parentData); |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
private function handleDiscriminatorProperty(): void |
352
|
|
|
{ |
353
|
|
|
if ( |
354
|
|
|
$this->discriminatorMap |
|
|
|
|
355
|
|
|
&& !$this->getReflection()->isAbstract() |
356
|
|
|
&& !$this->getReflection()->isInterface() |
357
|
|
|
) { |
358
|
|
|
if (false === $typeValue = array_search($this->name, $this->discriminatorMap, true)) { |
359
|
|
|
throw new InvalidMetadataException(sprintf( |
360
|
|
|
'The sub-class "%s" is not listed in the discriminator of the base class "%s".', |
361
|
|
|
$this->name, |
362
|
|
|
$this->discriminatorBaseClass, |
363
|
|
|
)); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
$this->discriminatorValue = $typeValue; |
367
|
|
|
|
368
|
|
|
if ( |
369
|
|
|
isset($this->propertyMetadata[$this->discriminatorFieldName]) |
370
|
|
|
&& !$this->propertyMetadata[$this->discriminatorFieldName] instanceof StaticPropertyMetadata |
371
|
|
|
) { |
372
|
|
|
throw new InvalidMetadataException(sprintf( |
373
|
|
|
'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".', |
374
|
|
|
$this->discriminatorFieldName, |
375
|
|
|
$this->discriminatorBaseClass, |
376
|
|
|
$this->name, |
377
|
|
|
)); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
$discriminatorProperty = new StaticPropertyMetadata( |
381
|
|
|
$this->name, |
382
|
|
|
$this->discriminatorFieldName, |
383
|
|
|
$typeValue, |
384
|
|
|
$this->discriminatorGroups, |
385
|
|
|
); |
386
|
|
|
$discriminatorProperty->serializedName = $this->discriminatorFieldName; |
387
|
|
|
$discriminatorProperty->xmlAttribute = $this->xmlDiscriminatorAttribute; |
388
|
|
|
$discriminatorProperty->xmlElementCData = $this->xmlDiscriminatorCData; |
389
|
|
|
$discriminatorProperty->xmlNamespace = $this->xmlDiscriminatorNamespace; |
390
|
|
|
$this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty; |
391
|
|
|
} |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
private function sortProperties(): void |
395
|
|
|
{ |
396
|
|
|
switch ($this->accessorOrder) { |
397
|
|
|
case self::ACCESSOR_ORDER_UNDEFINED: |
398
|
|
|
$this->propertyMetadata = (new IdenticalPropertyOrderingStrategy())->order($this->propertyMetadata); |
399
|
|
|
break; |
400
|
|
|
|
401
|
|
|
case self::ACCESSOR_ORDER_ALPHABETICAL: |
402
|
|
|
$this->propertyMetadata = (new AlphabeticalPropertyOrderingStrategy())->order($this->propertyMetadata); |
403
|
|
|
break; |
404
|
|
|
|
405
|
|
|
case self::ACCESSOR_ORDER_CUSTOM: |
406
|
|
|
$this->propertyMetadata = (new CustomPropertyOrderingStrategy($this->customOrder))->order($this->propertyMetadata); |
407
|
|
|
break; |
408
|
|
|
} |
409
|
|
|
} |
410
|
|
|
} |
411
|
|
|
|
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.