Completed
Push — master ( a5a1ff...7fe571 )
by Christoffer
05:12 queued 01:59
created

TypeHelper   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 162
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 162
rs 9.8
c 0
b 0
f 0
wmc 31

4 Methods

Rating   Name   Duplication   Size   Complexity  
B isEqualType() 0 19 6
C isTypeSubtypeOf() 0 46 11
B doTypesOverlap() 0 30 5
C compareTypes() 0 22 9
1
<?php
2
3
namespace Digia\GraphQL\Util;
4
5
use Digia\GraphQL\Error\InvalidTypeException;
6
use Digia\GraphQL\Language\Node\ListTypeNode;
7
use Digia\GraphQL\Language\Node\NamedTypeNode;
8
use Digia\GraphQL\Language\Node\NonNullTypeNode;
9
use Digia\GraphQL\Language\Node\TypeNodeInterface;
10
use Digia\GraphQL\Type\Definition\AbstractTypeInterface;
11
use Digia\GraphQL\Type\Definition\LeafTypeInterface;
12
use Digia\GraphQL\Type\Definition\ListType;
13
use Digia\GraphQL\Type\Definition\NonNullType;
14
use Digia\GraphQL\Type\Definition\ObjectType;
15
use Digia\GraphQL\Type\Definition\TypeInterface;
16
use function Digia\GraphQL\Type\newList;
17
use function Digia\GraphQL\Type\newNonNull;
18
use Digia\GraphQL\Schema\SchemaInterface;
19
20
class TypeHelper
21
{
22
    /**
23
     * Provided two types, return true if the types are equal (invariant).
24
     *
25
     * @param TypeInterface $typeA
26
     * @param TypeInterface $typeB
27
     * @return bool
28
     */
29
    public function isEqualType(TypeInterface $typeA, TypeInterface $typeB): bool
30
    {
31
        // Equivalent types are equal.
32
        if ($typeA === $typeB) {
33
            return true;
34
        }
35
36
        // If either type is non-null, the other must also be non-null.
37
        if ($typeA instanceof NonNullType && $typeB instanceof NonNullType) {
38
            return $this->isEqualType($typeA->getOfType(), $typeB->getOfType());
39
        }
40
41
        // If either type is a list, the other must also be a list.
42
        if ($typeA instanceof ListType && $typeB instanceof ListType) {
43
            return $this->isEqualType($typeA->getOfType(), $typeB->getOfType());
44
        }
45
46
        // Otherwise the types are not equal.
47
        return false;
48
    }
49
50
    /**
51
     * Provided a type and a super type, return true if the first type is either
52
     * equal or a subset of the second super type (covariant).
53
     *
54
     * @param SchemaInterface $schema
55
     * @param TypeInterface   $maybeSubtype
56
     * @param TypeInterface   $superType
57
     * @return bool
58
     */
59
    public function isTypeSubtypeOf(
60
        SchemaInterface $schema,
61
        TypeInterface $maybeSubtype,
62
        TypeInterface $superType
63
    ): bool {
64
        // Equivalent type is a valid subtype.
65
        if ($maybeSubtype === $superType) {
66
            return true;
67
        }
68
69
        // If superType is non-null, maybeSubType must also be non-null.
70
        if ($superType instanceof NonNullType) {
71
            if ($maybeSubtype instanceof NonNullType) {
72
                return $this->isTypeSubtypeOf($schema, $maybeSubtype->getOfType(), $superType->getOfType());
73
            }
74
            return false;
75
        }
76
77
        if ($maybeSubtype instanceof NonNullType) {
78
            // If superType is nullable, maybeSubType may be non-null or nullable.
79
            return $this->isTypeSubtypeOf($schema, $maybeSubtype->getOfType(), $superType);
80
        }
81
82
        // If superType type is a list, maybeSubType type must also be a list.
83
        if ($superType instanceof ListType) {
84
            if ($maybeSubtype instanceof ListType) {
85
                return $this->isTypeSubtypeOf($schema, $maybeSubtype->getOfType(), $superType->getOfType());
86
            }
87
            return false;
88
        }
89
90
        if ($maybeSubtype instanceof ListType) {
91
            // If superType is not a list, maybeSubType must also be not a list.
92
            return false;
93
        }
94
95
        // If superType type is an abstract type, maybeSubType type may be a currently
96
        // possible object type.
97
        if ($superType instanceof AbstractTypeInterface &&
98
            $maybeSubtype instanceof ObjectType &&
99
            $schema->isPossibleType($superType, $maybeSubtype)) {
100
            return true;
101
        }
102
103
        // Otherwise, the child type is not a valid subtype of the parent type.
104
        return false;
105
    }
106
107
    /**
108
     * Provided two composite types, determine if they "overlap". Two composite
109
     * types overlap when the Sets of possible concrete types for each intersect.
110
     *
111
     * This is often used to determine if a fragment of a given type could possibly
112
     * be visited in a context of another type.
113
     *
114
     * @param SchemaInterface $schema
115
     * @param TypeInterface   $typeA
116
     * @param TypeInterface   $typeB
117
     * @return bool
118
     */
119
    public function doTypesOverlap(SchemaInterface $schema, TypeInterface $typeA, TypeInterface $typeB): bool
120
    {
121
        // Equivalent types overlap
122
        if ($typeA === $typeB) {
123
            return true;
124
        }
125
126
        if ($typeA instanceof AbstractTypeInterface) {
127
            if ($typeB instanceof AbstractTypeInterface) {
128
                // If both types are abstract, then determine if there is any intersection
129
                // between possible concrete types of each.
130
                return arraySome($schema->getPossibleTypes($typeA),
131
                    function (TypeInterface $type) use ($schema, $typeB) {
132
                        return $schema->isPossibleType($typeB, $type);
133
                    });
134
            }
135
136
            // Determine if the latter type is a possible concrete type of the former.
137
            /** @noinspection PhpParamsInspection */
138
            return $schema->isPossibleType($typeA, $typeB);
139
        }
140
141
        if ($typeB instanceof AbstractTypeInterface) {
142
            // Determine if the former type is a possible concrete type of the latter.
143
            /** @noinspection PhpParamsInspection */
144
            return $schema->isPossibleType($typeB, $typeA);
145
        }
146
147
        // Otherwise the types do not overlap.
148
        return false;
149
    }
150
151
    /**
152
     * Two types conflict if both types could not apply to a value simultaneously.
153
     * Composite types are ignored as their individual field types will be compared
154
     * later recursively. However List and Non-Null types must match.
155
     *
156
     * @param TypeInterface $typeA
157
     * @param TypeInterface $typeB
158
     * @return bool
159
     */
160
    public function compareTypes(TypeInterface $typeA, TypeInterface $typeB): bool
161
    {
162
        if ($typeA instanceof ListType) {
163
            return $typeB instanceof ListType
164
                ? $this->compareTypes($typeA->getOfType(), $typeB->getOfType())
165
                : true;
166
        }
167
        if ($typeB instanceof ListType) {
168
            return true;
169
        }
170
        if ($typeA instanceof NonNullType) {
171
            return $typeB instanceof NonNullType
172
                ? $this->compareTypes($typeA->getOfType(), $typeB->getOfType())
173
                : true;
174
        }
175
        if ($typeB instanceof NonNullType) {
176
            return true;
177
        }
178
        if ($typeA instanceof LeafTypeInterface || $typeB instanceof LeafTypeInterface) {
179
            return $typeA !== $typeB;
180
        }
181
        return false;
182
    }
183
}
184