Completed
Push — master ( 02a711...d87a08 )
by Chris
02:47
created

MatchTester::isWeakScalarMatch()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 8
cts 8
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 8
nc 4
nop 2
crap 5
1
<?php declare(strict_types = 1);
2
3
namespace DaveRandom\CallbackValidator;
4
5
final class MatchTester
6
{
7
    /**
8
     * Thou shalt not instantiate
9
     */
10
    private function __construct() { }
11
12
    /**
13
     * Lookup table of all built-in types
14
     */
15
    private static $builtInTypes = [
16
        BuiltInTypes::STRING   => true,
17
        BuiltInTypes::INT      => true,
18
        BuiltInTypes::FLOAT    => true,
19
        BuiltInTypes::BOOL     => true,
20
        BuiltInTypes::ARRAY    => true,
21
        BuiltInTypes::CALLABLE => true,
22
        BuiltInTypes::VOID     => true,
23
        BuiltInTypes::ITERABLE => true,
24
    ];
25
26
    /**
27
     * Lookup table of scalar types
28
     */
29
    private static $scalarTypes = [
30
        BuiltInTypes::STRING => true,
31
        BuiltInTypes::INT    => true,
32
        BuiltInTypes::FLOAT  => true,
33
        BuiltInTypes::BOOL   => true,
34
    ];
35
36
    /**
37
     * @param string $superTypeName
38
     * @param string $subTypeName
39
     * @return bool
40
     */
41 16
    public static function isWeakScalarMatch($superTypeName, $subTypeName)
42
    {
43
        // Nothing else satisfies array, callable, void or iterable
44 16
        if (!isset(self::$scalarTypes[$superTypeName])) {
45 4
            return false;
46
        }
47
48
        // Scalars can all cast to each other
49 12
        if (isset(self::$scalarTypes[$subTypeName])) {
50 6
            return true;
51
        }
52
53
        // Classes with __toString() satisfy string
54 6
        if ($superTypeName === BuiltInTypes::STRING && \method_exists($subTypeName, '__toString')) {
55 3
            return true;
56
        }
57
58 3
        return false;
59
    }
60
61
    /**
62
     * @param string|null $superTypeName
63
     * @param bool $superTypeNullable
64
     * @param string|null $subTypeName
65
     * @param bool $subTypeNullable
66
     * @param bool $weak
67
     * @return bool
68
     */
69 90
    public static function isMatch($superTypeName, $superTypeNullable, $subTypeName, $subTypeNullable, $weak)
70
    {
71
        // If the super type is unspecified, anything is a match
72 90
        if ($superTypeName === null) {
73
            return true;
74
        }
75
76
        // If the sub type is unspecified, nothing is a match
77 90
        if ($subTypeName === null) {
78
            return false;
79
        }
80
81 90
        $superTypeName = (string)$superTypeName;
82 90
        $subTypeName = (string)$subTypeName;
83
84
        // Sub type cannot be nullable unless the super type is as well
85 90
        if ($subTypeNullable && !$superTypeNullable) {
86
            // nullable void doesn't really make sense but for completeness...
87 26
            return $superTypeName === BuiltInTypes::VOID && $subTypeName === BuiltInTypes::VOID;
88
        }
89
90
        // If the string is an exact match it's definitely acceptable
91 68
        if ($superTypeName === $subTypeName) {
92 10
            return true;
93
        }
94
95
        // Check iterable
96 64 View Code Duplication
        if ($superTypeName === BuiltInTypes::ITERABLE) {
97 24
            return $subTypeName === BuiltInTypes::ARRAY
98 24
                || $subTypeName === \Traversable::class
99 24
                || \is_subclass_of($subTypeName, \Traversable::class);
100
        }
101
102
        // Check callable
103 46 View Code Duplication
        if ($superTypeName === BuiltInTypes::CALLABLE) {
104 24
            return $subTypeName === \Closure::class
105 24
                || \method_exists($subTypeName, '__invoke')
106 24
                || \is_subclass_of($subTypeName, \Closure::class);
107
        }
108
109
        // If the super type is built-in, check whether casting rules can succeed
110 28
        if (isset(self::$builtInTypes[$superTypeName])) {
111
            // Fail immediately in strict mode
112 28
            return $weak && self::isWeakScalarMatch($superTypeName, $subTypeName);
113
        }
114
115
        // We now know the super type is not built-in and there's no string match, sub type must not be built-in
116
        if (isset(self::$builtInTypes[$subTypeName])) {
117
            return false;
118
        }
119
120
        return \is_subclass_of($subTypeName, $superTypeName);
121
    }
122
}
123