TypeIsCovariant::__invoke()   C
last analyzed

Complexity

Conditions 17
Paths 16

Size

Total Lines 66
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 29
nc 16
nop 2
dl 0
loc 66
rs 5.2166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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