Passed
Pull Request — master (#158)
by Christoffer
04:03
created

TypeHelper::isEqualType()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 7
nc 4
nop 2
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\ListType;
12
use Digia\GraphQL\Type\Definition\NonNullType;
13
use Digia\GraphQL\Type\Definition\ObjectType;
14
use Digia\GraphQL\Type\Definition\TypeInterface;
15
use function Digia\GraphQL\Type\GraphQLList;
16
use function Digia\GraphQL\Type\GraphQLNonNull;
17
use Digia\GraphQL\Type\SchemaInterface;
18
19
class TypeHelper
20
{
21
    /**
22
     * Provided two types, return true if the types are equal (invariant).
23
     *
24
     * @param TypeInterface $typeA
25
     * @param TypeInterface $typeB
26
     * @return bool
27
     */
28
    public function isEqualType(TypeInterface $typeA, TypeInterface $typeB): bool
29
    {
30
        // Equivalent types are equal.
31
        if ($typeA === $typeB) {
32
            return true;
33
        }
34
35
        // If either type is non-null, the other must also be non-null.
36
        if ($typeA instanceof NonNullType && $typeB instanceof NonNullType) {
37
            return $this->isEqualType($typeA->getOfType(), $typeB->getOfType());
38
        }
39
40
        // If either type is a list, the other must also be a list.
41
        if ($typeA instanceof ListType && $typeB instanceof ListType) {
42
            return $this->isEqualType($typeA->getOfType(), $typeB->getOfType());
43
        }
44
45
        // Otherwise the types are not equal.
46
        return false;
47
    }
48
49
    /**
50
     * Provided a type and a super type, return true if the first type is either
51
     * equal or a subset of the second super type (covariant).
52
     *
53
     * @param SchemaInterface $schema
54
     * @param TypeInterface   $maybeSubtype
55
     * @param TypeInterface   $superType
56
     * @return bool
57
     */
58
    public function isTypeSubtypeOf(
59
        SchemaInterface $schema,
60
        TypeInterface $maybeSubtype,
61
        TypeInterface $superType
62
    ): bool {
63
        // Equivalent type is a valid subtype.
64
        if ($maybeSubtype === $superType) {
65
            return true;
66
        }
67
68
        // If superType is non-null, maybeSubType must also be non-null.
69
        if ($superType instanceof NonNullType) {
70
            if ($maybeSubtype instanceof NonNullType) {
71
                return $this->isTypeSubtypeOf($schema, $maybeSubtype->getOfType(), $superType->getOfType());
72
            }
73
            return false;
74
        }
75
76
        if ($maybeSubtype instanceof NonNullType) {
77
            // If superType is nullable, maybeSubType may be non-null or nullable.
78
            return $this->isTypeSubtypeOf($schema, $maybeSubtype->getOfType(), $superType);
79
        }
80
81
        // If superType type is a list, maybeSubType type must also be a list.
82
        if ($superType instanceof ListType) {
83
            if ($maybeSubtype instanceof ListType) {
84
                return $this->isTypeSubtypeOf($schema, $maybeSubtype->getOfType(), $superType->getOfType());
85
            }
86
            return false;
87
        }
88
89
        if ($maybeSubtype instanceof ListType) {
90
            // If superType is not a list, maybeSubType must also be not a list.
91
            return false;
92
        }
93
94
        // If superType type is an abstract type, maybeSubType type may be a currently
95
        // possible object type.
96
        if ($superType instanceof AbstractTypeInterface &&
97
            $maybeSubtype instanceof ObjectType &&
98
            $schema->isPossibleType($superType, $maybeSubtype)) {
99
            return true;
100
        }
101
102
        // Otherwise, the child type is not a valid subtype of the parent type.
103
        return false;
104
    }
105
106
    /**
107
     * Provided two composite types, determine if they "overlap". Two composite
108
     * types overlap when the Sets of possible concrete types for each intersect.
109
     *
110
     * This is often used to determine if a fragment of a given type could possibly
111
     * be visited in a context of another type.
112
     *
113
     * @param SchemaInterface $schema
114
     * @param TypeInterface   $typeA
115
     * @param TypeInterface   $typeB
116
     * @return bool
117
     */
118
    public function doTypesOverlap(SchemaInterface $schema, TypeInterface $typeA, TypeInterface $typeB): bool
119
    {
120
        // Equivalent types overlap
121
        if ($typeA === $typeB) {
122
            return true;
123
        }
124
125
        if ($typeA instanceof AbstractTypeInterface) {
126
            if ($typeB instanceof AbstractTypeInterface) {
127
                // If both types are abstract, then determine if there is any intersection
128
                // between possible concrete types of each.
129
                return arraySome($schema->getPossibleTypes($typeA),
130
                    function (TypeInterface $type) use ($schema, $typeB) {
131
                        return $schema->isPossibleType($typeB, $type);
132
                    });
133
            }
134
135
            // Determine if the latter type is a possible concrete type of the former.
136
            /** @noinspection PhpParamsInspection */
137
            return $schema->isPossibleType($typeA, $typeB);
138
        }
139
140
        if ($typeB instanceof AbstractTypeInterface) {
141
            // Determine if the former type is a possible concrete type of the latter.
142
            /** @noinspection PhpParamsInspection */
143
            return $schema->isPossibleType($typeB, $typeA);
144
        }
145
146
        // Otherwise the types do not overlap.
147
        return false;
148
    }
149
150
    /**
151
     * Given a Schema and an AST node describing a type, return a GraphQLType
152
     * definition which applies to that type. For example, if provided the parsed
153
     * AST node for `[User]`, a GraphQLList instance will be returned, containing
154
     * the type called "User" found in the schema. If a type called "User" is not
155
     * found in the schema, then undefined will be returned.
156
     *
157
     * @param SchemaInterface   $schema
158
     * @param TypeNodeInterface $typeNode
159
     * @return TypeInterface|null
160
     * @throws InvalidTypeException
161
     */
162
    public function fromAST(SchemaInterface $schema, TypeNodeInterface $typeNode): ?TypeInterface
163
    {
164
        $innerType = null;
165
166
        if ($typeNode instanceof ListTypeNode) {
167
            $innerType = $this->fromAST($schema, $typeNode->getType());
168
            return null !== $innerType ? GraphQLList($innerType) : null;
169
        }
170
171
        if ($typeNode instanceof NonNullTypeNode) {
172
            $innerType = $this->fromAST($schema, $typeNode->getType());
173
            return null !== $innerType ? GraphQLNonNull($innerType) : null;
174
        }
175
176
        if ($typeNode instanceof NamedTypeNode) {
177
            return $schema->getType($typeNode->getNameValue());
178
        }
179
180
        throw new InvalidTypeException(sprintf('Unexpected type kind: %s', $typeNode->getKind()));
181
    }
182
}
183