Completed
Push — master ( a11a2f...a1ae8b )
by Asmir
03:55
created

ClassMetadata   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 316
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 57.36%

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 4
dl 0
loc 316
ccs 113
cts 197
cp 0.5736
rs 8.4864
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A setDiscriminator() 0 15 3
A setAccessorOrder() 0 16 4
A addPropertyMetadata() 0 8 3
A addPreSerializeMethod() 0 4 1
A addPostSerializeMethod() 0 4 1
A addPostDeserializeMethod() 0 4 1
A addHandlerCallback() 0 4 1
C merge() 0 81 14
A registerNamespace() 0 17 4
B serialize() 0 28 1
B unserialize() 0 44 6
D sortProperties() 0 31 9

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
302
            $this->xmlDiscriminatorAttribute = $deserializedData['xmlDiscriminatorAttribute'];
303
        }
304
305
        if (isset($deserializedData['xmlDiscriminatorNamespace'])) {
306
            $this->xmlDiscriminatorNamespace = $deserializedData['xmlDiscriminatorNamespace'];
307
        }
308
309
        if (isset($deserializedData['xmlDiscriminatorCData'])) {
310
            $this->xmlDiscriminatorCData = $deserializedData['xmlDiscriminatorCData'];
311
        }
312
313
        parent::unserialize($parentStr);
314
    }
315
316 315
    private function sortProperties()
317
    {
318 315
        switch ($this->accessorOrder) {
319 315
            case self::ACCESSOR_ORDER_ALPHABETICAL:
320 10
                ksort($this->propertyMetadata);
321 10
                break;
322
323 312
            case self::ACCESSOR_ORDER_CUSTOM:
324 46
                $order = $this->customOrder;
325 46
                $currentSorting = $this->propertyMetadata ? array_combine(array_keys($this->propertyMetadata), range(1, count($this->propertyMetadata))) : [];
326 46
                uksort($this->propertyMetadata, function ($a, $b) use ($order, $currentSorting) {
327 46
                    $existsA = isset($order[$a]);
328 46
                    $existsB = isset($order[$b]);
329
330 46
                    if (!$existsA && !$existsB) {
331 5
                        return $currentSorting[$a] - $currentSorting[$b];
332
                    }
333
334 45
                    if (!$existsA) {
335 5
                        return 1;
336
                    }
337
338 44
                    if (!$existsB) {
339 5
                        return -1;
340
                    }
341
342 39
                    return $order[$a] < $order[$b] ? -1 : 1;
343 46
                });
344 46
                break;
345 315
        }
346 315
    }
347
}
348