Passed
Pull Request — master (#1549)
by
unknown
02:36
created

UnionHandler::deserializeUnion()   D

Complexity

Conditions 19
Paths 145

Size

Total Lines 68
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 19
eloc 45
c 6
b 0
f 0
nc 145
nop 4
dl 0
loc 68
rs 4.1416

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\Handler;
6
7
use JMS\Serializer\Context;
8
use JMS\Serializer\DeserializationContext;
9
use JMS\Serializer\Exception\NonFloatCastableTypeException;
10
use JMS\Serializer\Exception\NonIntCastableTypeException;
11
use JMS\Serializer\Exception\NonStringCastableTypeException;
12
use JMS\Serializer\Exception\NonVisitableTypeException;
13
use JMS\Serializer\Exception\PropertyMissingException;
14
use JMS\Serializer\Exception\RuntimeException;
15
use JMS\Serializer\GraphNavigatorInterface;
16
use JMS\Serializer\SerializationContext;
17
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
18
use JMS\Serializer\Visitor\SerializationVisitorInterface;
19
20
final class UnionHandler implements SubscribingHandlerInterface
21
{
22
    private static $aliases = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float'];
0 ignored issues
show
introduced by
The private property $aliases is not used, and could be removed.
Loading history...
23
    private bool $requireAllProperties;
24
25
    public function __construct(bool $requireAllProperties = false)
26
    {
27
        $this->requireAllProperties = $requireAllProperties;
28
    }
29
30
    /**
31
     * {@inheritdoc}
32
     */
33
    public static function getSubscribingMethods()
34
    {
35
        $methods = [];
36
        $formats = ['json', 'xml'];
37
38
        foreach ($formats as $format) {
39
            $methods[] = [
40
                'type' => 'union',
41
                'format' => $format,
42
                'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
43
                'method' => 'deserializeUnion',
44
            ];
45
            $methods[] = [
46
                'type' => 'union',
47
                'format' => $format,
48
                'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
49
                'method' => 'serializeUnion',
50
            ];
51
        }
52
53
        return $methods;
54
    }
55
56
    public function serializeUnion(
57
        SerializationVisitorInterface $visitor,
0 ignored issues
show
Unused Code introduced by
The parameter $visitor is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

57
        /** @scrutinizer ignore-unused */ SerializationVisitorInterface $visitor,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
58
        mixed $data,
59
        array $type,
60
        SerializationContext $context
61
    ): mixed {
62
        if ($this->isPrimitiveType(gettype($data))) {
63
            return $this->matchSimpleType($data, $type, $context);
64
        } else {
65
            $resolvedType = [
66
                'name' => get_class($data),
67
                'params' => [],
68
            ];
69
70
            return $context->getNavigator()->accept($data, $resolvedType);
71
        }
72
    }
73
74
    public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context): mixed
75
    {
76
        if ($data instanceof \SimpleXMLElement) {
77
            throw new RuntimeException('XML deserialisation into union types is not supported yet.');
78
        }
79
80
        $alternativeName = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $alternativeName is dead and can be removed.
Loading history...
81
82
        foreach ($type['params'] as $possibleType) {
83
            $propertyMetadata = $context->getMetadataStack()->top();
84
            $finalType = null;
85
            if (null !== $propertyMetadata->unionDiscriminatorField) {
86
                if (!array_key_exists($propertyMetadata->unionDiscriminatorField, $data)) {
87
                    throw new NonVisitableTypeException("Union Discriminator Field '$propertyMetadata->unionDiscriminatorField' not found in data");
88
                }
89
90
                $lkup = $data[$propertyMetadata->unionDiscriminatorField];
91
                if (!empty($propertyMetadata->unionDiscriminatorMap)) {
92
                    if (array_key_exists($lkup, $propertyMetadata->unionDiscriminatorMap)) {
93
                        $finalType = [
94
                            'name' => $propertyMetadata->unionDiscriminatorMap[$lkup],
95
                            'params' => [],
96
                        ];
97
                    } else {
98
                        throw new NonVisitableTypeException("Union Discriminator Map does not contain key '$lkup'");
99
                    }
100
                } else {
101
                    $finalType = [
102
                        'name' => $lkup,
103
                        'params' => [],
104
                    ];
105
                }
106
            }
107
108
            if (null !== $finalType && null !== $finalType['name']) {
109
                return $context->getNavigator()->accept($data, $finalType);
110
            } else {
111
                try {
112
                    $previousVisitorRequireSetting = $visitor->getRequireAllRequiredProperties();
113
                    if ($this->requireAllProperties) {
114
                        $visitor->setRequireAllRequiredProperties($this->requireAllProperties);
115
                    }
116
117
                    if ($this->isPrimitiveType($possibleType['name']) && (is_array($data) || !$this->testPrimitive($data, $possibleType['name'], $context->getFormat()))) {
118
                        continue;
119
                    }
120
121
                    $accept = $context->getNavigator()->accept($data, $possibleType);
122
                    if ($this->requireAllProperties) {
123
                        $visitor->setRequireAllRequiredProperties($previousVisitorRequireSetting);
124
                    }
125
126
                    return $accept;
127
                } catch (NonVisitableTypeException $e) {
128
                    continue;
129
                } catch (PropertyMissingException $e) {
130
                    continue;
131
                } catch (NonStringCastableTypeException $e) {
132
                    continue;
133
                } catch (NonIntCastableTypeException $e) {
134
                    continue;
135
                } catch (NonFloatCastableTypeException $e) {
136
                    continue;
137
                }
138
            }
139
        }
140
141
        return null;
142
    }
143
144
    private function matchSimpleType(mixed $data, array $type, Context $context): mixed
145
    {
146
        $alternativeName = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $alternativeName is dead and can be removed.
Loading history...
147
148
        foreach ($type['params'] as $possibleType) {
149
            if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) {
150
                continue;
151
            }
152
153
            try {
154
                return $context->getNavigator()->accept($data, $possibleType);
155
            } catch (NonVisitableTypeException $e) {
156
                continue;
157
            } catch (PropertyMissingException $e) {
158
                continue;
159
            }
160
        }
161
162
        return null;
163
    }
164
165
    private function isPrimitiveType(string $type): bool
166
    {
167
        return in_array($type, ['int', 'integer', 'float', 'double', 'bool', 'boolean', 'string']);
168
    }
169
170
    private function testPrimitive(mixed $data, string $type, string $format): bool
0 ignored issues
show
Unused Code introduced by
The parameter $format is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

170
    private function testPrimitive(mixed $data, string $type, /** @scrutinizer ignore-unused */ string $format): bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
171
    {
172
        switch ($type) {
173
            case 'integer':
174
            case 'int':
175
                return (string) (int) $data === (string) $data;
176
177
            case 'double':
178
            case 'float':
179
                return (string) (float) $data === (string) $data;
180
181
            case 'bool':
182
            case 'boolean':
183
                return (string) (bool) $data === (string) $data;
184
185
            case 'string':
186
                return (string) $data === (string) $data;
187
        }
188
189
        return false;
190
    }
191
}
192