Passed
Pull Request — master (#1546)
by
unknown
02:44
created

UnionHandler::matchSimpleType()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 7
nc 6
nop 3
dl 0
loc 12
rs 9.6111
c 1
b 0
f 0
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\RuntimeException;
10
use JMS\Serializer\GraphNavigatorInterface;
11
use JMS\Serializer\SerializationContext;
12
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
13
use JMS\Serializer\Visitor\SerializationVisitorInterface;
14
15
final class UnionHandler implements SubscribingHandlerInterface
16
{
17
    static $aliases = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float'];
18
19
    /**
20
     * {@inheritdoc}
21
     */
22
    public static function getSubscribingMethods()
23
    {
24
        $methods = [];
25
        $formats = ['json', 'xml'];
26
27
        foreach ($formats as $format) {
28
            $methods[] = [
29
                'type' => 'union',
30
                'format' => $format,
31
                'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
32
                'method' => 'deserializeUnion',
33
            ];
34
            $methods[] = [
35
                'type' => 'union',
36
                'format' => $format,
37
                'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
38
                'method' => 'serializeUnion',
39
            ];
40
        }
41
42
        return $methods;
43
    }
44
45
    public function serializeUnion(
46
        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

46
        /** @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...
47
        mixed $data,
48
        array $type,
49
        SerializationContext $context
50
    ) {
51
        return $this->matchSimpleType($data, $type, $context);
52
    }
53
54
    /**
55
     * @param mixed $data
56
     * @param array $type
57
     */
58
    public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context)
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

58
    public function deserializeUnion(/** @scrutinizer ignore-unused */ DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context)

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...
59
    {
60
        if ($data instanceof \SimpleXMLElement) {
61
            throw new RuntimeException('XML deserialisation into union types is not supported yet.');
62
        }
63
64
        return $this->matchSimpleType($data, $type, $context);
65
    }
66
67
    private function matchSimpleType(mixed $data, array $type, Context $context)
68
    {
69
        $dataType = $this->determineType($data, $type, $context->getFormat());
70
        $alternativeName = null;
71
72
        if (isset($aliases[$dataType])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $aliases seems to never exist and therefore isset should always be false.
Loading history...
73
            $alternativeName = $aliases[$dataType];
74
        }
75
76
        foreach ($type['params'] as $possibleType) {
77
            if ($possibleType['name'] === $dataType || $possibleType['name'] === $alternativeName) {
78
                return $context->getNavigator()->accept($data, $possibleType);
79
            }
80
        }
81
    }
82
83
    /**
84
     *  ReflectionUnionType::getTypes() returns the types sorted according to these rules:
85
     * - Classes, interfaces, traits, iterable (replaced by Traversable), ReflectionIntersectionType objects, parent and self:
86
     *     these types will be returned first, in the order in which they were declared.
87
     * - static and all built-in types (iterable replaced by array) will come next. They will always be returned in this order:
88
     *     static, callable, array, string, int, float, bool (or false or true), null.
89
     *
90
     * For determining types of primitives, it is necessary to reorder primitives so that they are tested from lowest specificity to highest:
91
     * i.e. null, true, false, int, float, bool, string
92
     */
93
    private function reorderTypes(array $type): array
94
    {
95
        if ($type['params']) {
96
            uasort($type['params'], static function ($a, $b) {
97
                $order = ['null' => 0, 'true' => 1, 'false' => 2, 'bool' => 3, 'int' => 4, 'float' => 5, 'string' => 6];
98
99
                return (array_key_exists($a['name'], $order) ? $order[$a['name']] : 7) <=> (array_key_exists($b['name'], $order) ? $order[$b['name']] : 7);
100
            });
101
        }
102
103
        return $type;
104
    }
105
106
    private function determineType(mixed $data, array $type, string $format): string
107
    {
108
        foreach ($this->reorderTypes($type)['params'] as $possibleType) {
109
            if ($this->testPrimitive($data, $possibleType['name'], $format)) {
110
                return $possibleType['name'];
111
            }
112
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
113
    }
114
115
    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

115
    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...
116
    {
117
        switch ($type) {
118
            case 'integer':
119
            case 'int':
120
                return (string) (int) $data === (string) $data;
121
122
            case 'double':
123
            case 'float':
124
                return (string) (float) $data === (string) $data;
125
126
            case 'bool':
127
            case 'boolean':
128
                return (string) (bool) $data === (string) $data;
129
130
            case 'string':
0 ignored issues
show
introduced by
The function implicitly returns null when this case condition does not match. This is incompatible with the type-hinted return boolean. Consider adding a default case to the switch.
Loading history...
131
                return (string) $data === (string) $data;
132
        }
133
    }
134
}
135