Passed
Pull Request — master (#1606)
by Ivan
07:56
created

ClassMetadata::merge()   C

Complexity

Conditions 12
Paths 69

Size

Total Lines 62
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 65.9495

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 12
eloc 40
c 1
b 0
f 1
nc 69
nop 1
dl 0
loc 62
ccs 12
cts 43
cp 0.2791
crap 65.9495
rs 6.9666

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
 * @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 $discriminatorDefaultClass = null;
102 39
    /**
103
     * @var string
104 39
     */
105
    public $discriminatorFieldName;
106
    /**
107
     * @var string
108 39
     */
109 34
    public $discriminatorValue;
110 34
111
    /**
112
     * @var string[]
113
     */
114 39
    public $discriminatorMap = [];
115 39
116 39
    /**
117 39
     * @var string[]
118
     */
119 282
    public $discriminatorGroups = [];
120
121 282
    /**
122 282
     * @var bool
123 282
     */
124 26
    public $xmlDiscriminatorAttribute = false;
125
126 282
    /**
127
     * @var bool
128 2
     */
129
    public $xmlDiscriminatorCData = true;
130 2
131 2
    /**
132
     * @var string
133 2
     */
134
    public $xmlDiscriminatorNamespace;
135 2
136 2
    /**
137
     * @var string|Expression
138 4
     */
139
    public $excludeIf;
140 4
141 4
    public function setDiscriminator(string $fieldName, array $map, array $groups = [], $defaultClass = null): void
142
    {
143 27
        if (empty($fieldName)) {
144
            throw new InvalidMetadataException('The $fieldName cannot be empty.');
145 27
        }
146
147
        if (empty($map)) {
148 27
            throw new InvalidMetadataException('The discriminator map cannot be empty.');
149
        }
150 27
151 27
        $this->discriminatorBaseClass = $this->name;
152 27
        $this->discriminatorDefaultClass = $defaultClass;
153 27
        $this->discriminatorFieldName = $fieldName;
154 27
        $this->discriminatorMap = $map;
155 27
        $this->discriminatorGroups = $groups;
156
157 27
        $this->handleDiscriminatorProperty();
158 2
    }
159 2
160
    private function getReflection(): \ReflectionClass
161
    {
162 27
        return new \ReflectionClass($this->name);
163
    }
164
165
    /**
166
     * Sets the order of properties in the class.
167
     *
168
     * @param array $customOrder
169 27
     *
170 2
     * @throws InvalidMetadataException When the accessor order is not valid.
171 2
     * @throws InvalidMetadataException When the custom order is not valid.
172
     */
173
    public function setAccessorOrder(string $order, array $customOrder = []): void
174 27
    {
175 27
        if (!in_array($order, [self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM], true)) {
176
            throw new InvalidMetadataException(sprintf('The accessor order "%s" is invalid.', $order));
177
        }
178 27
179 2
        foreach ($customOrder as $name) {
180 2
            if (!\is_string($name)) {
181 2
                throw new InvalidMetadataException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name)));
182
            }
183
        }
184 27
185 15
        $this->accessorOrder = $order;
186
        $this->customOrder = array_flip($customOrder);
187
        $this->sortProperties();
188
    }
189
190
    public function addPropertyMetadata(BasePropertyMetadata $metadata): void
191
    {
192
        parent::addPropertyMetadata($metadata);
193 15
194
        $this->sortProperties();
195 15
        if ($metadata instanceof PropertyMetadata && $metadata->excludeIf) {
196 15
            $this->usingExpression = true;
197
        }
198
    }
199
200
    public function addPreSerializeMethod(MethodMetadata $method): void
201
    {
202
        $this->preSerializeMethods[] = $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
221
        parent::merge($object);
222 31
223
        $this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods);
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->xmlRootPrefix = $object->xmlRootPrefix;
228 31
        $this->xmlRootNamespace = $object->xmlRootNamespace;
229 31
        if (null !== $object->excludeIf) {
230 31
            $this->excludeIf = $object->excludeIf;
231
        }
232
233 2
        $this->isMap = $object->isMap;
234
        $this->isList = $object->isList;
235
236 31
        $this->xmlNamespaces = array_merge($this->xmlNamespaces, $object->xmlNamespaces);
237 31
238
        if ($object->accessorOrder) {
239
            $this->accessorOrder = $object->accessorOrder;
240
            $this->customOrder = $object->customOrder;
241
        }
242
243
        if ($object->discriminatorFieldName && $this->discriminatorFieldName && $object->discriminatorFieldName !== $this->discriminatorFieldName) {
244
            throw new InvalidMetadataException(sprintf(
245
                '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.',
246
                $object->name,
247
                $this->discriminatorBaseClass,
248
                $this->discriminatorBaseClass,
249
            ));
250
        } elseif (!$this->discriminatorFieldName && $object->discriminatorFieldName) {
251
            $this->discriminatorFieldName = $object->discriminatorFieldName;
252
            $this->discriminatorMap = $object->discriminatorMap;
253
        }
254
255
        if (null !== $object->discriminatorDisabled) {
256
            $this->discriminatorDisabled = $object->discriminatorDisabled;
257
        }
258
259
        if (null !== $object->discriminatorDefaultClass) {
260
            $this->discriminatorDefaultClass = $object->discriminatorDefaultClass;
261
        }
262
263
        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...
264
            $this->discriminatorFieldName = $object->discriminatorFieldName;
265
            $this->discriminatorMap = $object->discriminatorMap;
266
            $this->discriminatorBaseClass = $object->discriminatorBaseClass;
267
            $this->discriminatorGroups = $object->discriminatorGroups;
268
269
            $this->xmlDiscriminatorCData = $object->xmlDiscriminatorCData;
270
            $this->xmlDiscriminatorAttribute = $object->xmlDiscriminatorAttribute;
271
            $this->xmlDiscriminatorNamespace = $object->xmlDiscriminatorNamespace;
272
        }
273
274
        $this->handleDiscriminatorProperty();
275
276
        $this->sortProperties();
277
    }
278
279
    public function registerNamespace(string $uri, ?string $prefix = null): void
280
    {
281
        if (!\is_string($uri)) {
0 ignored issues
show
introduced by
The condition is_string($uri) is always true.
Loading history...
282
            throw new InvalidMetadataException(sprintf('$uri is expected to be a strings, but got value %s.', json_encode($uri)));
283
        }
284
285
        if (null !== $prefix) {
286
            if (!\is_string($prefix)) {
0 ignored issues
show
introduced by
The condition is_string($prefix) is always true.
Loading history...
287
                throw new InvalidMetadataException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix)));
288
            }
289
        } else {
290
            $prefix = '';
291
        }
292
293
        $this->xmlNamespaces[$prefix] = $uri;
294
    }
295
296
    protected function serializeToArray(): array
297
    {
298
        $this->sortProperties();
299
300
        return [
301
            $this->preSerializeMethods,
302
            $this->postSerializeMethods,
303
            $this->postDeserializeMethods,
304
            $this->xmlRootName,
305
            $this->xmlRootNamespace,
306
            $this->xmlNamespaces,
307
            $this->accessorOrder,
308
            $this->customOrder,
309
            $this->discriminatorDisabled,
310
            $this->discriminatorBaseClass,
311
            $this->discriminatorFieldName,
312
            $this->discriminatorValue,
313
            $this->discriminatorMap,
314
            $this->discriminatorGroups,
315
            $this->excludeIf,
316
            $this->discriminatorGroups,
317
            $this->xmlDiscriminatorAttribute,
318
            $this->xmlDiscriminatorCData,
319 286
            $this->usingExpression,
320
            $this->xmlDiscriminatorNamespace,
321 286
            $this->xmlRootPrefix,
322 286
            $this->isList,
323
            $this->isMap,
324
            parent::serializeToArray(),
325
            $this->discriminatorDefaultClass,
326 286
        ];
327 7
    }
328 7
329
    protected function unserializeFromArray(array $data): void
330 284
    {
331 34
        [
332 34
            $this->preSerializeMethods,
333
            $this->postSerializeMethods,
334 286
            $this->postDeserializeMethods,
335
            $this->xmlRootName,
336
            $this->xmlRootNamespace,
337
            $this->xmlNamespaces,
338
            $this->accessorOrder,
339
            $this->customOrder,
340
            $this->discriminatorDisabled,
341
            $this->discriminatorBaseClass,
342
            $this->discriminatorFieldName,
343
            $this->discriminatorValue,
344
            $this->discriminatorMap,
345
            $this->discriminatorGroups,
346
            $this->excludeIf,
347
            $this->discriminatorGroups,
348
            $this->xmlDiscriminatorAttribute,
349
            $this->xmlDiscriminatorCData,
350
            $this->usingExpression,
351
            $this->xmlDiscriminatorNamespace,
352
            $this->xmlRootPrefix,
353
            $this->isList,
354
            $this->isMap,
355
            $parentData,
356
            $this->discriminatorDefaultClass,
357
        ] = $data;
358
359
        parent::unserializeFromArray($parentData);
360
    }
361
362
    private function handleDiscriminatorProperty(): void
363
    {
364
        if (
365
            $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...
366
            && !$this->getReflection()->isAbstract()
367
            && !$this->getReflection()->isInterface()
368
        ) {
369
            if (false === $typeValue = array_search($this->name, $this->discriminatorMap, true)) {
370
                if (! empty($this->discriminatorDefaultClass)) {
371
                    $typeValue = $this->discriminatorDefaultClass;
372
                } else {
373
                    throw new InvalidMetadataException(sprintf(
374
                        'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
375
                        $this->name,
376
                        $this->discriminatorBaseClass,
377
                    ));
378
                }
379
            }
380
381
            $this->discriminatorValue = $typeValue;
382
383
            if (
384
                isset($this->propertyMetadata[$this->discriminatorFieldName])
385
                && !$this->propertyMetadata[$this->discriminatorFieldName] instanceof StaticPropertyMetadata
386
            ) {
387
                throw new InvalidMetadataException(sprintf(
388
                    'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".',
389
                    $this->discriminatorFieldName,
390
                    $this->discriminatorBaseClass,
391
                    $this->name,
392
                ));
393
            }
394
395
            $discriminatorProperty = new StaticPropertyMetadata(
396
                $this->name,
397
                $this->discriminatorFieldName,
398
                $typeValue,
399
                $this->discriminatorGroups,
400
            );
401
            $discriminatorProperty->serializedName = $this->discriminatorFieldName;
402
            $discriminatorProperty->xmlAttribute = $this->xmlDiscriminatorAttribute;
403
            $discriminatorProperty->xmlElementCData = $this->xmlDiscriminatorCData;
404
            $discriminatorProperty->xmlNamespace = $this->xmlDiscriminatorNamespace;
405
            $this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty;
406
        }
407
    }
408
409
    private function sortProperties(): void
410
    {
411
        switch ($this->accessorOrder) {
412
            case self::ACCESSOR_ORDER_UNDEFINED:
413
                $this->propertyMetadata = (new IdenticalPropertyOrderingStrategy())->order($this->propertyMetadata);
414
                break;
415
416
            case self::ACCESSOR_ORDER_ALPHABETICAL:
417
                $this->propertyMetadata = (new AlphabeticalPropertyOrderingStrategy())->order($this->propertyMetadata);
418
                break;
419
420
            case self::ACCESSOR_ORDER_CUSTOM:
421
                $this->propertyMetadata = (new CustomPropertyOrderingStrategy($this->customOrder))->order($this->propertyMetadata);
422
                break;
423
        }
424
    }
425
}
426