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

TypedPropertiesDriver::shouldTypeHintUnion()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 4
nop 1
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace JMS\Serializer\Metadata\Driver;
6
7
use JMS\Serializer\Metadata\ClassMetadata as SerializerClassMetadata;
8
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
9
use JMS\Serializer\Metadata\PropertyMetadata;
10
use JMS\Serializer\Metadata\StaticPropertyMetadata;
11
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
12
use JMS\Serializer\Type\Parser;
13
use JMS\Serializer\Type\ParserInterface;
14
use Metadata\ClassMetadata;
15
use Metadata\Driver\DriverInterface;
16
use ReflectionClass;
17
use ReflectionException;
18
use ReflectionMethod;
19
use ReflectionNamedType;
20
use ReflectionProperty;
21
use ReflectionType;
22
23
class TypedPropertiesDriver implements DriverInterface
24
{
25
    /**
26
     * @var DriverInterface
27
     */
28
    protected $delegate;
29
30
    /**
31
     * @var ParserInterface
32
     */
33
    protected $typeParser;
34
35
    /**
36
     * @var string[]
37
     */
38
    private $allowList;
39
40
    /**
41
     * @param string[] $allowList
42
     */
43
    public function __construct(DriverInterface $delegate, ?ParserInterface $typeParser = null, array $allowList = [])
44
    {
45
        $this->delegate = $delegate;
46
        $this->typeParser = $typeParser ?: new Parser();
47
        $this->allowList = array_merge($allowList, $this->getDefaultWhiteList());
48
    }
49
50
    private function getDefaultWhiteList(): array
51
    {
52
        return [
53
            'int',
54
            'float',
55
            'bool',
56
            'boolean',
57
            'string',
58
            'double',
59
            'iterable',
60
            'resource',
61
        ];
62
    }
63
64
    /**
65
     * @return SerializerClassMetadata|null
66
     */
67
    public function loadMetadataForClass(ReflectionClass $class): ?ClassMetadata
68
    {
69
        $classMetadata = $this->delegate->loadMetadataForClass($class);
70
71
        if (null === $classMetadata) {
72
            return null;
73
        }
74
75
        \assert($classMetadata instanceof SerializerClassMetadata);
76
77
        // We base our scan on the internal driver's property list so that we
78
        // respect any internal allow/blocklist like in the AnnotationDriver
79
        foreach ($classMetadata->propertyMetadata as $propertyMetadata) {
80
            // If the inner driver provides a type, don't guess anymore.
81
            if ($propertyMetadata->type) {
82
                continue;
83
            }
84
85
            try {
86
                $reflectionType = $this->getReflectionType($propertyMetadata);
87
88
                if ($this->shouldTypeHint($reflectionType)) {
89
                    $type = $reflectionType->getName();
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

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

89
                    /** @scrutinizer ignore-call */ 
90
                    $type = $reflectionType->getName();
Loading history...
90
91
                    $propertyMetadata->setType($this->typeParser->parse($type));
92
                } elseif ($this->shouldTypeHintUnion($reflectionType)) {
93
                    $propertyMetadata->setType([
94
                        'name' => 'union',
95
                        'params' => array_map(fn (string $type) => $this->typeParser->parse($type), $reflectionType->getTypes()),
0 ignored issues
show
Bug introduced by
The method getTypes() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionUnionType. ( Ignorable by Annotation )

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

95
                        'params' => array_map(fn (string $type) => $this->typeParser->parse($type), $reflectionType->/** @scrutinizer ignore-call */ getTypes()),
Loading history...
96
                    ]);
97
                }
98
            } catch (ReflectionException $e) {
99
                continue;
100
            }
101
        }
102
103
        return $classMetadata;
104
    }
105
106
    private function getReflectionType(PropertyMetadata $propertyMetadata): ?ReflectionType
107
    {
108
        if ($this->isNotSupportedVirtualProperty($propertyMetadata)) {
109
            return null;
110
        }
111
112
        if ($propertyMetadata instanceof VirtualPropertyMetadata) {
113
            return (new ReflectionMethod($propertyMetadata->class, $propertyMetadata->getter))
114
                ->getReturnType();
115
        }
116
117
        return (new ReflectionProperty($propertyMetadata->class, $propertyMetadata->name))
118
            ->getType();
119
    }
120
121
    private function isNotSupportedVirtualProperty(PropertyMetadata $propertyMetadata): bool
122
    {
123
        return $propertyMetadata instanceof StaticPropertyMetadata
124
            || $propertyMetadata instanceof ExpressionPropertyMetadata;
125
    }
126
127
    /**
128
     * @phpstan-assert-if-true \ReflectionNamedType $reflectionType
129
     */
130
    private function shouldTypeHint(?ReflectionType $reflectionType): bool
131
    {
132
        if (!$reflectionType instanceof ReflectionNamedType) {
133
            return false;
134
        }
135
136
        if (in_array($reflectionType->getName(), $this->allowList, true)) {
137
            return true;
138
        }
139
140
        return class_exists($reflectionType->getName())
141
            || interface_exists($reflectionType->getName());
142
    }
143
144
    /**
145
     * @phpstan-assert-if-true \ReflectionUnionType $reflectionType
146
     */
147
    private function shouldTypeHintUnion(?ReflectionType $reflectionType)
148
    {
149
        if (!$reflectionType instanceof \ReflectionUnionType) {
150
            return false;
151
        }
152
153
        foreach ($reflectionType->getTypes() as $type) {
154
            if ($this->shouldTypeHint($type)) {
155
                return true;
156
            }
157
        }
158
159
        return false;
160
    }
161
}
162