Passed
Pull Request — master (#1546)
by
unknown
03:05
created

UnionHandler::getSubscribingMethods()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 14
nc 2
nop 0
dl 0
loc 20
rs 9.7998
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
     * {@inheritdoc}
20
     */
21
    public static function getSubscribingMethods()
22
    {
23
        $methods = [];
24
        $formats = ['json', 'xml'];
25
26
        foreach ($formats as $format) {
27
            $methods[] = [
28
                'type' => 'union',
29
                'format' => $format,
30
                'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
31
                'method' => 'deserializeUnion',
32
            ];
33
            $methods[] = [
34
                'type' => 'union',
35
                'format' => $format,
36
                'direction' => GraphNavigatorInterface::DIRECTION_SERIALIZATION,
37
                'method' => 'serializeUnion',
38
            ];
39
        }
40
        return $methods;
41
    }
42
43
    public function serializeUnion(
44
        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

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

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

109
    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...
110
        switch($type) {
111
            case 'integer':
112
            case 'int':
113
                return (string)(int)$data === (string)$data;
114
            case 'double':
115
            case 'float':
116
                return (string)(float)$data === (string)$data;
117
            case 'bool':
118
            case 'boolean':
119
                return (string)(bool)$data === (string)$data;
120
            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...
121
                return (string)$data === (string)$data;
122
        }
123
    }
124
}
125