Completed
Pull Request — master (#1303)
by Ayrton
04:48
created

ClassMetadata::merge()   B

Complexity

Conditions 10
Paths 37

Size

Total Lines 51
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 32.1774

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 33
c 2
b 0
f 0
nc 37
nop 1
dl 0
loc 51
ccs 15
cts 38
cp 0.3947
crap 32.1774
rs 7.6666

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
                throw new InvalidMetadataException(sprintf(
405
                    'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
406
                    $this->name,
407
                    $this->discriminatorBaseClass
408
                ));
409
            }
410
411
            $this->discriminatorValue = $typeValue;
412
413
            if (
414
                isset($this->propertyMetadata[$this->discriminatorFieldName])
415
                && !$this->propertyMetadata[$this->discriminatorFieldName] instanceof StaticPropertyMetadata
416
            ) {
417
                throw new InvalidMetadataException(sprintf(
418
                    'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".',
419
                    $this->discriminatorFieldName,
420
                    $this->discriminatorBaseClass,
421
                    $this->name
422
                ));
423
            }
424
425
            $discriminatorProperty = new StaticPropertyMetadata(
426
                $this->name,
427
                $this->discriminatorFieldName,
428
                $typeValue,
429
                $this->discriminatorGroups
430
            );
431
            $discriminatorProperty->serializedName = $this->discriminatorFieldName;
432
            $discriminatorProperty->xmlAttribute = $this->xmlDiscriminatorAttribute;
433
            $discriminatorProperty->xmlElementCData = $this->xmlDiscriminatorCData;
434
            $discriminatorProperty->xmlNamespace = $this->xmlDiscriminatorNamespace;
435
            $this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty;
436
        }
437
    }
438
439
    private function sortProperties(): void
440
    {
441
        switch ($this->accessorOrder) {
442
            case self::ACCESSOR_ORDER_UNDEFINED:
443
                $this->propertyMetadata = (new IdenticalPropertyOrderingStrategy())->order($this->propertyMetadata);
444
                break;
445
446
            case self::ACCESSOR_ORDER_ALPHABETICAL:
447
                $this->propertyMetadata = (new AlphabeticalPropertyOrderingStrategy())->order($this->propertyMetadata);
448
                break;
449
450
            case self::ACCESSOR_ORDER_CUSTOM:
451
                $this->propertyMetadata = (new CustomPropertyOrderingStrategy($this->customOrder))->order($this->propertyMetadata);
452
                break;
453
        }
454
    }
455
}
456