TypeIsContravariant   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 58
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 26
dl 0
loc 58
rs 10
c 0
b 0
f 0
wmc 14

1 Method

Rating   Name   Duplication   Size   Complexity  
C __invoke() 0 56 14
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Roave\BackwardCompatibility\DetectChanges\Variance;
6
7
use Roave\BackwardCompatibility\Support\ArrayHelpers;
8
use Roave\BetterReflection\Reflection\ReflectionType;
9
use function strtolower;
10
11
/**
12
 * This is a simplistic contravariant type check. A more appropriate approach would be to
13
 * have a `$type->includes($otherType)` check with actual types represented as value objects,
14
 * but that is a massive piece of work that should be done by importing an external library
15
 * instead, if this class no longer suffices.
16
 */
17
final class TypeIsContravariant
18
{
19
    public function __invoke(
20
        ?ReflectionType $type,
21
        ?ReflectionType $comparedType
22
    ) : bool {
23
        if ($comparedType === null) {
24
            return true;
25
        }
26
27
        if ($type === null) {
28
            // nothing can be contravariant to `mixed` besides `mixed` itself (handled above)
29
            return false;
30
        }
31
32
        if ($type->allowsNull() && ! $comparedType->allowsNull()) {
33
            return false;
34
        }
35
36
        $typeAsString         = $type->__toString();
37
        $comparedTypeAsString = $comparedType->__toString();
38
39
        if (strtolower($typeAsString) === strtolower($comparedTypeAsString)) {
40
            return true;
41
        }
42
43
        if ($typeAsString === 'void') {
44
            // everything is always contravariant to `void`
45
            return true;
46
        }
47
48
        if ($comparedTypeAsString === 'object' && ! $type->isBuiltin()) {
49
            // `object` is always contravariant to any object type
50
            return true;
51
        }
52
53
        if ($comparedTypeAsString === 'iterable' && $typeAsString === 'array') {
54
            return true;
55
        }
56
57
        if ($type->isBuiltin() !== $comparedType->isBuiltin()) {
58
            return false;
59
        }
60
61
        if ($type->isBuiltin()) {
62
            // All other type declarations have no variance/contravariance relationship
63
            return false;
64
        }
65
66
        $typeReflectionClass = $type->targetReflectionClass();
67
68
        if ($comparedType->targetReflectionClass()->isInterface()) {
69
            return $typeReflectionClass->implementsInterface($comparedTypeAsString);
70
        }
71
72
        return ArrayHelpers::stringArrayContainsString(
73
            $comparedTypeAsString,
74
            $typeReflectionClass->getParentClassNames()
75
        );
76
    }
77
}
78