Completed
Pull Request — master (#1303)
by Ayrton
06:25
created

ClassMetadata::unserialize()   D

Complexity

Conditions 10
Paths 512

Size

Total Lines 60
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 10

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 37
c 2
b 0
f 0
nc 512
nop 1
dl 0
loc 60
ccs 3
cts 3
cp 1
crap 10
rs 4.1777

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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