Passed
Pull Request — master (#1366)
by Asmir
09:41
created

ClassMetadata::merge()   B

Complexity

Conditions 11
Paths 37

Size

Total Lines 58
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 33.1967

Importance

Changes 3
Bugs 0 Features 1
Metric Value
cc 11
eloc 38
c 3
b 0
f 1
nc 37
nop 1
dl 0
loc 58
rs 7.3166
ccs 19
cts 44
cp 0.4318
crap 33.1967

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 $xmlDiscriminatorAttribute = false;
119 282
120
    /**
121 282
     * @var bool
122 282
     */
123 282
    public $xmlDiscriminatorCData = true;
124 26
125
    /**
126 282
     * @var string
127
     */
128 2
    public $xmlDiscriminatorNamespace;
129
130 2
    /**
131 2
     * @var string|Expression
132
     */
133 2
    public $excludeIf;
134
135 2
    public function setDiscriminator(string $fieldName, array $map, array $groups = []): void
136 2
    {
137
        if (empty($fieldName)) {
138 4
            throw new InvalidMetadataException('The $fieldName cannot be empty.');
139
        }
140 4
141 4
        if (empty($map)) {
142
            throw new InvalidMetadataException('The discriminator map cannot be empty.');
143 27
        }
144
145 27
        $this->discriminatorBaseClass = $this->name;
146
        $this->discriminatorFieldName = $fieldName;
147
        $this->discriminatorMap = $map;
148 27
        $this->discriminatorGroups = $groups;
149
150 27
        $this->handleDiscriminatorProperty();
151 27
    }
152 27
153 27
    private function getReflection(): \ReflectionClass
154 27
    {
155 27
        return new \ReflectionClass($this->name);
156
    }
157 27
158 2
    /**
159 2
     * Sets the order of properties in the class.
160
     *
161
     * @param array $customOrder
162 27
     *
163
     * @throws InvalidMetadataException When the accessor order is not valid.
164
     * @throws InvalidMetadataException When the custom order is not valid.
165
     */
166
    public function setAccessorOrder(string $order, array $customOrder = []): void
167
    {
168
        if (!in_array($order, [self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM], true)) {
169 27
            throw new InvalidMetadataException(sprintf('The accessor order "%s" is invalid.', $order));
170 2
        }
171 2
172
        foreach ($customOrder as $name) {
173
            if (!\is_string($name)) {
174 27
                throw new InvalidMetadataException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name)));
175 27
            }
176
        }
177
178 27
        $this->accessorOrder = $order;
179 2
        $this->customOrder = array_flip($customOrder);
180 2
        $this->sortProperties();
181 2
    }
182
183
    public function addPropertyMetadata(BasePropertyMetadata $metadata): void
184 27
    {
185 15
        parent::addPropertyMetadata($metadata);
186
        $this->sortProperties();
187
        if ($metadata instanceof PropertyMetadata && $metadata->excludeIf) {
188
            $this->usingExpression = true;
189
        }
190
    }
191
192
    public function addPreSerializeMethod(MethodMetadata $method): void
193 15
    {
194
        $this->preSerializeMethods[] = $method;
195 15
    }
196 15
197
    public function addPostSerializeMethod(MethodMetadata $method): void
198
    {
199
        $this->postSerializeMethods[] = $method;
200
    }
201
202
    public function addPostDeserializeMethod(MethodMetadata $method): void
203
    {
204
        $this->postDeserializeMethods[] = $method;
205
    }
206 15
207 15
    public function merge(MergeableInterface $object): void
208 15
    {
209 15
        if (!$object instanceof ClassMetadata) {
210 15
            throw new InvalidMetadataException('$object must be an instance of ClassMetadata.');
211
        }
212 15
213 15
        parent::merge($object);
214 15
215 15
        $this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods);
216 15
        $this->postSerializeMethods = array_merge($this->postSerializeMethods, $object->postSerializeMethods);
217
        $this->postDeserializeMethods = array_merge($this->postDeserializeMethods, $object->postDeserializeMethods);
218
        $this->xmlRootName = $object->xmlRootName;
219 27
        $this->xmlRootPrefix = $object->xmlRootPrefix;
220 27
        $this->xmlRootNamespace = $object->xmlRootNamespace;
221
        if (null !== $object->excludeIf) {
222 31
            $this->excludeIf = $object->excludeIf;
223
        }
224 31
225
        $this->isMap = $object->isMap;
226
        $this->isList = $object->isList;
227
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 && $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
            $this->xmlDiscriminatorCData = $object->xmlDiscriminatorCData;
258
            $this->xmlDiscriminatorAttribute = $object->xmlDiscriminatorAttribute;
259
            $this->xmlDiscriminatorNamespace = $object->xmlDiscriminatorNamespace;
260
        }
261
262
        $this->handleDiscriminatorProperty();
263
264
        $this->sortProperties();
265
    }
266
267
    public function registerNamespace(string $uri, ?string $prefix = null): void
268
    {
269
        if (!\is_string($uri)) {
0 ignored issues
show
introduced by
The condition is_string($uri) is always true.
Loading history...
270
            throw new InvalidMetadataException(sprintf('$uri is expected to be a strings, but got value %s.', json_encode($uri)));
271
        }
272
273
        if (null !== $prefix) {
274
            if (!\is_string($prefix)) {
0 ignored issues
show
introduced by
The condition is_string($prefix) is always true.
Loading history...
275
                throw new InvalidMetadataException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix)));
276
            }
277
        } else {
278
            $prefix = '';
279
        }
280
281
        $this->xmlNamespaces[$prefix] = $uri;
282
    }
283
284
    /**
285
     * @return string
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
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...
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