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

Argument   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 106
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 34
c 1
b 0
f 0
dl 0
loc 106
rs 10
wmc 22

8 Methods

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