Completed
Pull Request — master (#1194)
by Ivan
38:34 queued 23:35
created

ClassMetadata::addPreDeserializeMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
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
class ClassMetadata extends MergeableClassMetadata
23
{
24
    public const ACCESSOR_ORDER_UNDEFINED = 'undefined';
25
    public const ACCESSOR_ORDER_ALPHABETICAL = 'alphabetical';
26
    public const ACCESSOR_ORDER_CUSTOM = 'custom';
27
28
    /** @var \ReflectionMethod[] */
29
    public $preSerializeMethods = [];
30
31
    /** @var \ReflectionMethod[] */
32
    public $preDeserializeMethods = [];
33
34
    /** @var \ReflectionMethod[] */
35
    public $postSerializeMethods = [];
36
37
    /** @var \ReflectionMethod[] */
38
    public $postDeserializeMethods = [];
39
40
    /**
41
     * @var string
42
     */
43
    public $xmlRootName;
44
45
    /**
46
     * @var string
47
     */
48
    public $xmlRootNamespace;
49
50
    /**
51
     * @var string
52
     */
53
    public $xmlRootPrefix;
54
    /**
55
     * @var string[]
56
     */
57
    public $xmlNamespaces = [];
58
59
    /**
60
     * @var string
61
     */
62
    public $accessorOrder;
63
64
    /**
65
     * @var string[]
66
     */
67
    public $customOrder;
68
69
    /**
70
     * @internal
71
     *
72 32
     * @var bool
73
     */
74 32
    public $usingExpression = false;
75
76
    /**
77
     * @internal
78 32
     *
79
     * @var bool
80
     */
81
    public $isList = false;
82 32
83 32
    /**
84 32
     * @internal
85 32
     *
86 32
     * @var bool
87
     */
88 15
    public $isMap = false;
89
90 15
    /**
91
     * @var bool
92
     */
93
    public $discriminatorDisabled = false;
94
95
    /**
96
     * @var string
97
     */
98
    public $discriminatorBaseClass;
99
    /**
100
     * @var string
101
     */
102 39
    public $discriminatorFieldName;
103
    /**
104 39
     * @var string
105
     */
106
    public $discriminatorValue;
107
108 39
    /**
109 34
     * @var string[]
110 34
     */
111
    public $discriminatorMap = [];
112
113
    /**
114 39
     * @var string[]
115 39
     */
116 39
    public $discriminatorGroups = [];
117 39
118
    /**
119 282
     * @var bool
120
     */
121 282
    public $xmlDiscriminatorAttribute = false;
122 282
123 282
    /**
124 26
     * @var bool
125
     */
126 282
    public $xmlDiscriminatorCData = true;
127
128 2
    /**
129
     * @var string
130 2
     */
131 2
    public $xmlDiscriminatorNamespace;
132
133 2
    /**
134
     * @var string|Expression
135 2
     */
136 2
    public $excludeIf;
137
138 4
    public function setDiscriminator(string $fieldName, array $map, array $groups = []): void
139
    {
140 4
        if (empty($fieldName)) {
141 4
            throw new InvalidMetadataException('The $fieldName cannot be empty.');
142
        }
143 27
144
        if (empty($map)) {
145 27
            throw new InvalidMetadataException('The discriminator map cannot be empty.');
146
        }
147
148 27
        $this->discriminatorBaseClass = $this->name;
149
        $this->discriminatorFieldName = $fieldName;
150 27
        $this->discriminatorMap = $map;
151 27
        $this->discriminatorGroups = $groups;
152 27
153 27
        $this->handleDiscriminatorProperty();
154 27
    }
155 27
156
    private function getReflection(): \ReflectionClass
157 27
    {
158 2
        return new \ReflectionClass($this->name);
159 2
    }
160
161
    /**
162 27
     * Sets the order of properties in the class.
163
     *
164
     * @param array $customOrder
165
     *
166
     * @throws InvalidMetadataException When the accessor order is not valid.
167
     * @throws InvalidMetadataException When the custom order is not valid.
168
     */
169 27
    public function setAccessorOrder(string $order, array $customOrder = []): void
170 2
    {
171 2
        if (!in_array($order, [self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM], true)) {
172
            throw new InvalidMetadataException(sprintf('The accessor order "%s" is invalid.', $order));
173
        }
174 27
175 27
        foreach ($customOrder as $name) {
176
            if (!\is_string($name)) {
177
                throw new InvalidMetadataException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name)));
178 27
            }
179 2
        }
180 2
181 2
        $this->accessorOrder = $order;
182
        $this->customOrder = array_flip($customOrder);
183
        $this->sortProperties();
184 27
    }
185 15
186
    public function addPropertyMetadata(BasePropertyMetadata $metadata): void
187
    {
188
        parent::addPropertyMetadata($metadata);
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 addPreDeserializeMethod(MethodMetadata $method): void
201
    {
202
        $this->preDeserializeMethods[] = $method;
203
    }
204
205
    public function addPostSerializeMethod(MethodMetadata $method): void
206 15
    {
207 15
        $this->postSerializeMethods[] = $method;
208 15
    }
209 15
210 15
    public function addPostDeserializeMethod(MethodMetadata $method): void
211
    {
212 15
        $this->postDeserializeMethods[] = $method;
213 15
    }
214 15
215 15
    public function merge(MergeableInterface $object): void
216 15
    {
217
        if (!$object instanceof ClassMetadata) {
218
            throw new InvalidMetadataException('$object must be an instance of ClassMetadata.');
219 27
        }
220 27
        parent::merge($object);
221
222 31
        $this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods);
223
        $this->preDeserializeMethods = array_merge($this->preDeserializeMethods, $object->preDeserializeMethods);
224 31
        $this->postSerializeMethods = array_merge($this->postSerializeMethods, $object->postSerializeMethods);
225
        $this->postDeserializeMethods = array_merge($this->postDeserializeMethods, $object->postDeserializeMethods);
226
        $this->xmlRootName = $object->xmlRootName;
227
        $this->xmlRootNamespace = $object->xmlRootNamespace;
228 31
        $this->xmlNamespaces = array_merge($this->xmlNamespaces, $object->xmlNamespaces);
229 31
230 31
        if ($object->accessorOrder) {
231
            $this->accessorOrder = $object->accessorOrder;
232
            $this->customOrder = $object->customOrder;
233 2
        }
234
235
        if ($object->discriminatorFieldName && $this->discriminatorFieldName) {
236 31
            throw new InvalidMetadataException(sprintf(
237 31
                '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.',
238
                $object->name,
239
                $this->discriminatorBaseClass,
240
                $this->discriminatorBaseClass
241
            ));
242
        } elseif (!$this->discriminatorFieldName && $object->discriminatorFieldName) {
243
            $this->discriminatorFieldName = $object->discriminatorFieldName;
244
            $this->discriminatorMap = $object->discriminatorMap;
245
        }
246
247
        if (null !== $object->discriminatorDisabled) {
248
            $this->discriminatorDisabled = $object->discriminatorDisabled;
249
        }
250
251
        if ($object->discriminatorMap) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $object->discriminatorMap of type string[] 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...
252
            $this->discriminatorFieldName = $object->discriminatorFieldName;
253
            $this->discriminatorMap = $object->discriminatorMap;
254
            $this->discriminatorBaseClass = $object->discriminatorBaseClass;
255
            $this->discriminatorGroups = $object->discriminatorGroups;
256
        }
257
258
        $this->handleDiscriminatorProperty();
259
260
        $this->sortProperties();
261
    }
262
263
    public function registerNamespace(string $uri, ?string $prefix = null): void
264
    {
265
        if (!\is_string($uri)) {
0 ignored issues
show
introduced by
The condition is_string($uri) is always true.
Loading history...
266
            throw new InvalidMetadataException(sprintf('$uri is expected to be a strings, but got value %s.', json_encode($uri)));
267
        }
268
269
        if (null !== $prefix) {
270
            if (!\is_string($prefix)) {
0 ignored issues
show
introduced by
The condition is_string($prefix) is always true.
Loading history...
271
                throw new InvalidMetadataException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix)));
272
            }
273
        } else {
274
            $prefix = '';
275
        }
276
277
        $this->xmlNamespaces[$prefix] = $uri;
278
    }
279
280
    /**
281
     * @return string
282
     *
283
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
284
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint
285
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.UselessReturnAnnotation
286
     */
287
    public function serialize()
288
    {
289
        $this->sortProperties();
290
291
        return serialize([
292
            $this->preSerializeMethods,
293
            $this->preDeserializeMethods,
294
            $this->postSerializeMethods,
295
            $this->postDeserializeMethods,
296
            $this->xmlRootName,
297
            $this->xmlRootNamespace,
298
            $this->xmlNamespaces,
299
            $this->accessorOrder,
300
            $this->customOrder,
301
            $this->discriminatorDisabled,
302
            $this->discriminatorBaseClass,
303
            $this->discriminatorFieldName,
304
            $this->discriminatorValue,
305
            $this->discriminatorMap,
306
            $this->discriminatorGroups,
307
            $this->excludeIf,
308
            parent::serialize(),
309
            'discriminatorGroups' => $this->discriminatorGroups,
310
            'xmlDiscriminatorAttribute' => $this->xmlDiscriminatorAttribute,
311
            'xmlDiscriminatorCData' => $this->xmlDiscriminatorCData,
312
            'usingExpression' => $this->usingExpression,
313
            'xmlDiscriminatorNamespace' => $this->xmlDiscriminatorNamespace,
314
            'xmlRootPrefix' => $this->xmlRootPrefix,
315
            'isList' => $this->isList,
316
            'isMap' => $this->isMap,
317
        ]);
318
    }
319 286
320
    /**
321 286
     * @param string $str
322 286
     *
323
     * @return void
324
     *
325
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint
326 286
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint
327 7
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration.UselessReturnAnnotation
328 7
     */
329
    public function unserialize($str)
330 284
    {
331 34
        $unserialized = unserialize($str);
332 34
333
        [
334 286
            $this->preSerializeMethods,
335
            $this->preDeserializeMethods,
336
            $this->postSerializeMethods,
337
            $this->postDeserializeMethods,
338
            $this->xmlRootName,
339
            $this->xmlRootNamespace,
340
            $this->xmlNamespaces,
341
            $this->accessorOrder,
342
            $this->customOrder,
343
            $this->discriminatorDisabled,
344
            $this->discriminatorBaseClass,
345
            $this->discriminatorFieldName,
346
            $this->discriminatorValue,
347
            $this->discriminatorMap,
348
            $this->discriminatorGroups,
349
            $this->excludeIf,
350
            $parentStr,
351
        ] = $unserialized;
352
353
        if (isset($unserialized['discriminatorGroups'])) {
354
            $this->discriminatorGroups = $unserialized['discriminatorGroups'];
355
        }
356
        if (isset($unserialized['usingExpression'])) {
357
            $this->usingExpression = $unserialized['usingExpression'];
358
        }
359
360
        if (isset($unserialized['xmlDiscriminatorAttribute'])) {
361
            $this->xmlDiscriminatorAttribute = $unserialized['xmlDiscriminatorAttribute'];
362
        }
363
364
        if (isset($unserialized['xmlDiscriminatorNamespace'])) {
365
            $this->xmlDiscriminatorNamespace = $unserialized['xmlDiscriminatorNamespace'];
366
        }
367
368
        if (isset($unserialized['xmlDiscriminatorCData'])) {
369
            $this->xmlDiscriminatorCData = $unserialized['xmlDiscriminatorCData'];
370
        }
371
372
        if (isset($unserialized['xmlRootPrefix'])) {
373
            $this->xmlRootPrefix = $unserialized['xmlRootPrefix'];
374
        }
375
376
        if (isset($unserialized['isList'])) {
377
            $this->isList = $unserialized['isList'];
378
        }
379
380
        if (isset($unserialized['isMap'])) {
381
            $this->isMap = $unserialized['isMap'];
382
        }
383
384
        parent::unserialize($parentStr);
385
    }
386
387
    private function handleDiscriminatorProperty(): void
388
    {
389
        if ($this->discriminatorMap
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->discriminatorMap of type string[] 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...
390
            && !$this->getReflection()->isAbstract()
391
            && !$this->getReflection()->isInterface()
392
        ) {
393
            if (false === $typeValue = array_search($this->name, $this->discriminatorMap, true)) {
394
                throw new InvalidMetadataException(sprintf(
395
                    'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
396
                    $this->name,
397
                    $this->discriminatorBaseClass
398
                ));
399
            }
400
401
            $this->discriminatorValue = $typeValue;
402
403
            if (isset($this->propertyMetadata[$this->discriminatorFieldName])
404
                && !$this->propertyMetadata[$this->discriminatorFieldName] instanceof StaticPropertyMetadata
405
            ) {
406
                throw new InvalidMetadataException(sprintf(
407
                    'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".',
408
                    $this->discriminatorFieldName,
409
                    $this->discriminatorBaseClass,
410
                    $this->name
411
                ));
412
            }
413
414
            $discriminatorProperty = new StaticPropertyMetadata(
415
                $this->name,
416
                $this->discriminatorFieldName,
417
                $typeValue,
418
                $this->discriminatorGroups
419
            );
420
            $discriminatorProperty->serializedName = $this->discriminatorFieldName;
421
            $discriminatorProperty->xmlAttribute = $this->xmlDiscriminatorAttribute;
422
            $discriminatorProperty->xmlElementCData = $this->xmlDiscriminatorCData;
423
            $discriminatorProperty->xmlNamespace = $this->xmlDiscriminatorNamespace;
424
            $this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty;
425
        }
426
    }
427
428
    private function sortProperties(): void
429
    {
430
        switch ($this->accessorOrder) {
431
            case self::ACCESSOR_ORDER_UNDEFINED:
432
                $this->propertyMetadata = (new IdenticalPropertyOrderingStrategy())->order($this->propertyMetadata);
433
                break;
434
435
            case self::ACCESSOR_ORDER_ALPHABETICAL:
436
                $this->propertyMetadata = (new AlphabeticalPropertyOrderingStrategy())->order($this->propertyMetadata);
437
                break;
438
439
            case self::ACCESSOR_ORDER_CUSTOM:
440
                $this->propertyMetadata = (new CustomPropertyOrderingStrategy($this->customOrder))->order($this->propertyMetadata);
441
                break;
442
        }
443
    }
444
}
445