TypeIsCovariant   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 68
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 68
rs 10
c 0
b 0
f 0
wmc 17

1 Method

Rating   Name   Duplication   Size   Complexity  
C __invoke() 0 66 17
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 Traversable;
10
use function strtolower;
11
12
/**
13
 * This is a simplistic covariant type check. A more appropriate approach would be to
14
 * have a `$type->includes($otherType)` check with actual types represented as value objects,
15
 * but that is a massive piece of work that should be done by importing an external library
16
 * instead, if this class no longer suffices.
17
 */
18
final class TypeIsCovariant
19
{
20
    public function __invoke(
21
        ?ReflectionType $type,
22
        ?ReflectionType $comparedType
23
    ) : bool {
24
        if ($type === null) {
25
            // everything can be covariant to `mixed`
26
            return true;
27
        }
28
29
        if ($comparedType === null) {
30
            // widening a type is not covariant
31
            return false;
32
        }
33
34
        if ($comparedType->allowsNull() && ! $type->allowsNull()) {
35
            return false;
36
        }
37
38
        $typeAsString         = $type->__toString();
39
        $comparedTypeAsString = $comparedType->__toString();
40
41
        if (strtolower($typeAsString) === strtolower($comparedTypeAsString)) {
42
            return true;
43
        }
44
45
        if ($typeAsString === 'void') {
46
            // nothing is covariant to `void`
47
            return false;
48
        }
49
50
        if ($typeAsString === 'object' && ! $comparedType->isBuiltin()) {
51
            // `object` is not covariant to a defined class type
52
            return true;
53
        }
54
55
        if ($comparedTypeAsString === 'array' && $typeAsString === 'iterable') {
56
            // an `array` is a subset of an `iterable`, therefore covariant
57
            return true;
58
        }
59
60
        if ($typeAsString === 'iterable' && ! $comparedType->isBuiltin()) {
61
            if ($comparedType->targetReflectionClass()->implementsInterface(Traversable::class)) {
62
                // `iterable` can be restricted via any `Iterator` implementation
63
                return true;
64
            }
65
        }
66
67
        if ($type->isBuiltin() !== $comparedType->isBuiltin()) {
68
            // other known built-in types are never covariant with non-built-in types
69
            return false;
70
        }
71
72
        if ($type->isBuiltin()) {
73
            // all other built-in type declarations have no variance/contravariance relationship
74
            return false;
75
        }
76
77
        $comparedTypeReflectionClass = $comparedType->targetReflectionClass();
78
79
        if ($type->targetReflectionClass()->isInterface()) {
80
            return $comparedTypeReflectionClass->implementsInterface($typeAsString);
81
        }
82
83
        return ArrayHelpers::stringArrayContainsString(
84
            $typeAsString,
85
            $comparedTypeReflectionClass->getParentClassNames()
86
        );
87
    }
88
}
89