Passed
Pull Request — 1.x (#3)
by Kevin
01:27
created

Argument::allows()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Zenstruck\Callback;
4
5
/**
6
 * @author Kevin Bond <[email protected]>
7
 */
8
final class Argument
9
{
10
    /**
11
     * Allow type to match exactly {@see supports()}.
12
     */
13
    public const EXACT = 2;
14
15
    /**
16
     * If type is class, parent classes are supported {@see supports()}.
17
     */
18
    public const COVARIANCE = 4;
19
20
    /**
21
     * If type is class, child classes are supported {@see supports()}.
22
     */
23
    public const CONTRAVARIANCE = 8;
24
25
    private const TYPE_NORMALIZE_MAP = [
26
        'boolean' => 'bool',
27
        'integer' => 'int',
28
        'resource (closed)' => 'resource',
29
    ];
30
31
    /** @var \ReflectionParameter */
32
    private $parameter;
33
34
    /** @var \ReflectionNamedType[] */
35
    private $types = [];
0 ignored issues
show
introduced by
The private property $types is not used, and could be removed.
Loading history...
36
37
    public function __construct(\ReflectionParameter $parameter)
38
    {
39
        $this->parameter = $parameter;
40
    }
41
42
    public function type(): ?string
43
    {
44
        return $this->hasType() ? \implode('|', $this->types()) : null;
45
    }
46
47
    /**
48
     * @return string[]
49
     */
50
    public function types(): array
51
    {
52
        return \array_map(static function(\ReflectionNamedType $type) { return $type->getName(); }, $this->reflectionTypes());
53
    }
54
55
    public function hasType(): bool
56
    {
57
        return !empty($this->types());
58
    }
59
60
    public function isUnionType(): bool
61
    {
62
        return \count($this->types()) > 1;
63
    }
64
65
    /**
66
     * @param string $type    The type to check if this argument supports
67
     * @param int    $options {@see EXACT}, {@see COVARIANCE}, {@see CONTRAVARIANCE}
68
     *                        Bitwise disjunction of above is allowed
69
     */
70
    public function supports(string $type, int $options = self::EXACT|self::COVARIANCE): bool
71
    {
72
        if (!$this->hasType()) {
73
            // no type-hint so any type is supported
74
            return true;
75
        }
76
77
        if ('null' === \mb_strtolower($type) && $this->parameter->allowsNull()) {
78
            return true;
79
        }
80
81
        $type = self::TYPE_NORMALIZE_MAP[$type] ?? $type;
82
83
        foreach ($this->types() as $supportedType) {
84
            if ($options & self::EXACT && $supportedType === $type) {
85
                return true;
86
            }
87
88
            if ($options & self::COVARIANCE && \is_a($type, $supportedType, true)) {
89
                return true;
90
            }
91
92
            if ($options & self::CONTRAVARIANCE && \is_a($supportedType, $type, true)) {
93
                return true;
94
            }
95
        }
96
97
        return false;
98
    }
99
100
    /**
101
     * @param mixed $value
102
     */
103
    public function allows($value): bool
104
    {
105
        return $this->supports(\is_object($value) ? \get_class($value) : \gettype($value));
106
    }
107
108
    /**
109
     * @return \ReflectionNamedType[]
110
     */
111
    private function reflectionTypes(): array
112
    {
113
        if (!$type = $this->parameter->getType()) {
114
            return [];
115
        }
116
117
        if ($type instanceof \ReflectionNamedType) {
118
            return [$type];
119
        }
120
121
        /** @var \ReflectionUnionType $type */
122
        return $type->getTypes();
123
    }
124
}
125