Completed
Pull Request — master (#900)
by Michael
11:10 queued 08:18
created

ClassMetadata   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 296
Duplicated Lines 0 %

Test Coverage

Coverage 58.24%

Importance

Changes 0
Metric Value
dl 0
loc 296
ccs 99
cts 170
cp 0.5824
rs 8.439
c 0
b 0
f 0
wmc 47

11 Methods

Rating   Name   Duplication   Size   Complexity  
B unserialize() 0 42 6
D sortProperties() 0 29 9
B serialize() 0 25 1
A setAccessorOrder() 0 15 4
C merge() 0 77 14
A addPostDeserializeMethod() 0 3 1
A addPostSerializeMethod() 0 3 1
A registerNamespace() 0 15 4
A addPropertyMetadata() 0 6 3
A addPreSerializeMethod() 0 3 1
A setDiscriminator() 0 14 3

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
/*
6
 * Copyright 2016 Johannes M. Schmitt <[email protected]>
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
namespace JMS\Serializer\Metadata;
22
23
use JMS\Serializer\Exception\InvalidArgumentException;
24
use JMS\Serializer\Exception\LogicException;
25
use Metadata\MergeableClassMetadata;
26
use Metadata\MergeableInterface;
27
use Metadata\MethodMetadata;
28
use Metadata\PropertyMetadata as BasePropertyMetadata;
29
30
/**
31
 * Class Metadata used to customize the serialization process.
32
 *
33
 * @author Johannes M. Schmitt <[email protected]>
34
 */
35
class ClassMetadata extends MergeableClassMetadata
36
{
37
    const ACCESSOR_ORDER_UNDEFINED = 'undefined';
38
    const ACCESSOR_ORDER_ALPHABETICAL = 'alphabetical';
39
    const ACCESSOR_ORDER_CUSTOM = 'custom';
40
41
    /** @var \ReflectionMethod[] */
42
    public $preSerializeMethods = [];
43
44
    /** @var \ReflectionMethod[] */
45
    public $postSerializeMethods = [];
46
47
    /** @var \ReflectionMethod[] */
48
    public $postDeserializeMethods = [];
49
50
    public $xmlRootName;
51
    public $xmlRootNamespace;
52
    public $xmlNamespaces = [];
53
    public $accessorOrder;
54
    public $customOrder;
55
    public $usingExpression = false;
56
57
    public $discriminatorDisabled = false;
58
    public $discriminatorBaseClass;
59
    public $discriminatorFieldName;
60
    public $discriminatorValue;
61
    public $discriminatorMap = [];
62
    public $discriminatorGroups = [];
63
64
    public $xmlDiscriminatorAttribute = false;
65
    public $xmlDiscriminatorCData = true;
66
    public $xmlDiscriminatorNamespace;
67
68 32
    public function setDiscriminator($fieldName, array $map, array $groups = [])
69
    {
70 32
        if (empty($fieldName)) {
71
            throw new InvalidArgumentException('The $fieldName cannot be empty.');
72
        }
73
74 32
        if (empty($map)) {
75
            throw new InvalidArgumentException('The discriminator map cannot be empty.');
76
        }
77
78 32
        $this->discriminatorBaseClass = $this->name;
79 32
        $this->discriminatorFieldName = $fieldName;
80 32
        $this->discriminatorMap = $map;
81 32
        $this->discriminatorGroups = $groups;
82 32
    }
83
84
    /**
85
     * Sets the order of properties in the class.
86
     *
87
     * @param string $order
88
     * @param array $customOrder
89
     *
90
     * @throws InvalidArgumentException When the accessor order is not valid
91
     * @throws InvalidArgumentException When the custom order is not valid
92
     */
93 39
    public function setAccessorOrder($order, array $customOrder = [])
94
    {
95 39
        if (!in_array($order, [self::ACCESSOR_ORDER_UNDEFINED, self::ACCESSOR_ORDER_ALPHABETICAL, self::ACCESSOR_ORDER_CUSTOM], true)) {
96
            throw new InvalidArgumentException(sprintf('The accessor order "%s" is invalid.', $order));
97
        }
98
99 39
        foreach ($customOrder as $name) {
100 34
            if (!\is_string($name)) {
101 34
                throw new InvalidArgumentException(sprintf('$customOrder is expected to be a list of strings, but got element of value %s.', json_encode($name)));
102
            }
103
        }
104
105 39
        $this->accessorOrder = $order;
106 39
        $this->customOrder = array_flip($customOrder);
107 39
        $this->sortProperties();
108 39
    }
109
110 263
    public function addPropertyMetadata(BasePropertyMetadata $metadata)
111
    {
112 263
        parent::addPropertyMetadata($metadata);
113 263
        $this->sortProperties();
114 263
        if ($metadata instanceof PropertyMetadata && $metadata->excludeIf) {
115 26
            $this->usingExpression = true;
116
        }
117 263
    }
118
119 2
    public function addPreSerializeMethod(MethodMetadata $method)
120
    {
121 2
        $this->preSerializeMethods[] = $method;
122 2
    }
123
124 2
    public function addPostSerializeMethod(MethodMetadata $method)
125
    {
126 2
        $this->postSerializeMethods[] = $method;
127 2
    }
128
129 4
    public function addPostDeserializeMethod(MethodMetadata $method)
130
    {
131 4
        $this->postDeserializeMethods[] = $method;
132 4
    }
133
134 25
    public function merge(MergeableInterface $object)
135
    {
136 25
        if (!$object instanceof ClassMetadata) {
137
            throw new InvalidArgumentException('$object must be an instance of ClassMetadata.');
138
        }
139 25
        parent::merge($object);
140
141 25
        $this->preSerializeMethods = array_merge($this->preSerializeMethods, $object->preSerializeMethods);
142 25
        $this->postSerializeMethods = array_merge($this->postSerializeMethods, $object->postSerializeMethods);
143 25
        $this->postDeserializeMethods = array_merge($this->postDeserializeMethods, $object->postDeserializeMethods);
144 25
        $this->xmlRootName = $object->xmlRootName;
145 25
        $this->xmlRootNamespace = $object->xmlRootNamespace;
146 25
        $this->xmlNamespaces = array_merge($this->xmlNamespaces, $object->xmlNamespaces);
147
148 25
        if ($object->accessorOrder) {
149 2
            $this->accessorOrder = $object->accessorOrder;
150 2
            $this->customOrder = $object->customOrder;
151
        }
152
153 25
        if ($object->discriminatorFieldName && $this->discriminatorFieldName) {
154
            throw new LogicException(sprintf(
155
                '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.',
156
                $object->name,
157
                $this->discriminatorBaseClass,
158
                $this->discriminatorBaseClass
159
            ));
160 25
        } elseif (!$this->discriminatorFieldName && $object->discriminatorFieldName) {
161 2
            $this->discriminatorFieldName = $object->discriminatorFieldName;
162 2
            $this->discriminatorMap = $object->discriminatorMap;
163
        }
164
165 25
        if ($object->discriminatorDisabled !== null) {
166 25
            $this->discriminatorDisabled = $object->discriminatorDisabled;
167
        }
168
169 25
        if ($object->discriminatorMap) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $object->discriminatorMap of type array 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...
170 2
            $this->discriminatorFieldName = $object->discriminatorFieldName;
171 2
            $this->discriminatorMap = $object->discriminatorMap;
172 2
            $this->discriminatorBaseClass = $object->discriminatorBaseClass;
173
        }
174
175 25
        if ($this->discriminatorMap && !$this->reflection->isAbstract()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->discriminatorMap of type array 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...
176 15
            if (false === $typeValue = array_search($this->name, $this->discriminatorMap, true)) {
177
                throw new LogicException(sprintf(
178
                    'The sub-class "%s" is not listed in the discriminator of the base class "%s".',
179
                    $this->name,
180
                    $this->discriminatorBaseClass
181
                ));
182
            }
183
184 15
            $this->discriminatorValue = $typeValue;
185
186 15
            if (isset($this->propertyMetadata[$this->discriminatorFieldName])
187 15
                && !$this->propertyMetadata[$this->discriminatorFieldName] instanceof StaticPropertyMetadata
188
            ) {
189
                throw new LogicException(sprintf(
190
                    'The discriminator field name "%s" of the base-class "%s" conflicts with a regular property of the sub-class "%s".',
191
                    $this->discriminatorFieldName,
192
                    $this->discriminatorBaseClass,
193
                    $this->name
194
                ));
195
            }
196
197 15
            $discriminatorProperty = new StaticPropertyMetadata(
198 15
                $this->name,
199 15
                $this->discriminatorFieldName,
200 15
                $typeValue,
201 15
                $this->discriminatorGroups
202
            );
203 15
            $discriminatorProperty->serializedName = $this->discriminatorFieldName;
204 15
            $discriminatorProperty->xmlAttribute = $this->xmlDiscriminatorAttribute;
205 15
            $discriminatorProperty->xmlElementCData = $this->xmlDiscriminatorCData;
206 15
            $discriminatorProperty->xmlNamespace = $this->xmlDiscriminatorNamespace;
207 15
            $this->propertyMetadata[$this->discriminatorFieldName] = $discriminatorProperty;
208
        }
209
210 25
        $this->sortProperties();
211 25
    }
212
213 31
    public function registerNamespace($uri, $prefix = null)
214
    {
215 31
        if (!\is_string($uri)) {
216
            throw new InvalidArgumentException(sprintf('$uri is expected to be a strings, but got value %s.', json_encode($uri)));
217
        }
218
219 31
        if ($prefix !== null) {
220 31
            if (!\is_string($prefix)) {
221 31
                throw new InvalidArgumentException(sprintf('$prefix is expected to be a strings, but got value %s.', json_encode($prefix)));
222
            }
223
        } else {
224 2
            $prefix = "";
225
        }
226
227 31
        $this->xmlNamespaces[$prefix] = $uri;
228 31
    }
229
230
    public function serialize()
231
    {
232
        $this->sortProperties();
233
234
        return serialize([
235
            $this->preSerializeMethods,
236
            $this->postSerializeMethods,
237
            $this->postDeserializeMethods,
238
            $this->xmlRootName,
239
            $this->xmlRootNamespace,
240
            $this->xmlNamespaces,
241
            $this->accessorOrder,
242
            $this->customOrder,
243
            $this->discriminatorDisabled,
244
            $this->discriminatorBaseClass,
245
            $this->discriminatorFieldName,
246
            $this->discriminatorValue,
247
            $this->discriminatorMap,
248
            $this->discriminatorGroups,
249
            parent::serialize(),
250
            'discriminatorGroups' => $this->discriminatorGroups,
251
            'xmlDiscriminatorAttribute' => $this->xmlDiscriminatorAttribute,
252
            'xmlDiscriminatorCData' => $this->xmlDiscriminatorCData,
253
            'usingExpression' => $this->usingExpression,
254
            'xmlDiscriminatorNamespace' => $this->xmlDiscriminatorNamespace,
255
        ]);
256
    }
257
258
    public function unserialize($str)
259
    {
260
        $unserialized = unserialize($str);
261
262
        list(
263
            $this->preSerializeMethods,
264
            $this->postSerializeMethods,
265
            $this->postDeserializeMethods,
266
            $this->xmlRootName,
267
            $this->xmlRootNamespace,
268
            $this->xmlNamespaces,
269
            $this->accessorOrder,
270
            $this->customOrder,
271
            $this->discriminatorDisabled,
272
            $this->discriminatorBaseClass,
273
            $this->discriminatorFieldName,
274
            $this->discriminatorValue,
275
            $this->discriminatorMap,
276
            $this->discriminatorGroups,
277
            $parentStr
278
            ) = $unserialized;
279
280
        if (isset($unserialized['discriminatorGroups'])) {
281
            $this->discriminatorGroups = $unserialized['discriminatorGroups'];
282
        }
283
        if (isset($unserialized['usingExpression'])) {
284
            $this->usingExpression = $unserialized['usingExpression'];
285
        }
286
287
        if (isset($unserialized['xmlDiscriminatorAttribute'])) {
288
            $this->xmlDiscriminatorAttribute = $unserialized['xmlDiscriminatorAttribute'];
289
        }
290
291
        if (isset($unserialized['xmlDiscriminatorNamespace'])) {
292
            $this->xmlDiscriminatorNamespace = $unserialized['xmlDiscriminatorNamespace'];
293
        }
294
295
        if (isset($unserialized['xmlDiscriminatorCData'])) {
296
            $this->xmlDiscriminatorCData = $unserialized['xmlDiscriminatorCData'];
297
        }
298
299
        parent::unserialize($parentStr);
300
    }
301
302 267
    private function sortProperties()
303
    {
304 267
        switch ($this->accessorOrder) {
305 267
            case self::ACCESSOR_ORDER_ALPHABETICAL:
306 7
                ksort($this->propertyMetadata);
307 7
                break;
308
309 265
            case self::ACCESSOR_ORDER_CUSTOM:
310 34
                $order = $this->customOrder;
311 34
                $currentSorting = $this->propertyMetadata ? array_combine(array_keys($this->propertyMetadata), range(1, \count($this->propertyMetadata))) : [];
312 34
                uksort($this->propertyMetadata, function ($a, $b) use ($order, $currentSorting) {
313 34
                    $existsA = isset($order[$a]);
314 34
                    $existsB = isset($order[$b]);
315
316 34
                    if (!$existsA && !$existsB) {
317 4
                        return $currentSorting[$a] - $currentSorting[$b];
318
                    }
319
320 33
                    if (!$existsA) {
321 4
                        return 1;
322
                    }
323
324 29
                    if (!$existsB) {
325 1
                        return -1;
326
                    }
327
328 28
                    return $order[$a] < $order[$b] ? -1 : 1;
329 34
                });
330 34
                break;
331
        }
332 267
    }
333
}
334