ClassMetadata   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 384
Duplicated Lines 0 %

Test Coverage

Coverage 53.53%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 187
dl 0
loc 384
ccs 91
cts 170
cp 0.5353
rs 9.0399
c 1
b 0
f 1
wmc 42

13 Methods

Rating   Name   Duplication   Size   Complexity  
A unserializeFromArray() 0 30 1
A serializeToArray() 0 29 1
B handleDiscriminatorProperty() 0 40 7
A setAccessorOrder() 0 15 4
A getReflection() 0 3 1
A sortProperties() 0 14 4
A setDiscriminator() 0 16 3
B merge() 0 58 11
A addPostDeserializeMethod() 0 3 1
A addPostSerializeMethod() 0 3 1
A registerNamespace() 0 15 4
A addPropertyMetadata() 0 7 3
A addPreSerializeMethod() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ClassMetadata often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClassMetadata, and based on these observations, apply Extract Interface, too.

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