Passed
Push — master ( 11461b...d9477e )
by Christoffer
02:32
created

SchemaExtender   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 170
rs 10
c 0
b 0
f 0
wmc 21

4 Methods

Rating   Name   Duplication   Size   Complexity  
D createInfo() 0 88 13
A createContext() 0 21 1
B checkExtensionNode() 0 13 5
A extend() 0 21 2
1
<?php
2
3
namespace Digia\GraphQL\Schema\Extension;
4
5
use Digia\GraphQL\Error\ExtensionException;
6
use Digia\GraphQL\Language\Node\DirectiveDefinitionNode;
7
use Digia\GraphQL\Language\Node\DocumentNode;
8
use Digia\GraphQL\Language\Node\EnumTypeExtensionNode;
9
use Digia\GraphQL\Language\Node\InputObjectTypeExtensionNode;
10
use Digia\GraphQL\Language\Node\InterfaceTypeExtensionNode;
11
use Digia\GraphQL\Language\Node\NodeInterface;
12
use Digia\GraphQL\Language\Node\ObjectTypeExtensionNode;
13
use Digia\GraphQL\Language\Node\ScalarTypeExtensionNode;
14
use Digia\GraphQL\Language\Node\TypeDefinitionNodeInterface;
15
use Digia\GraphQL\Language\Node\UnionTypeExtensionNode;
16
use Digia\GraphQL\Schema\DefinitionBuilder;
17
use Digia\GraphQL\Schema\Resolver\ResolverRegistryInterface;
18
use Digia\GraphQL\Schema\Schema;
19
use Digia\GraphQL\Type\Definition\InterfaceType;
20
use Digia\GraphQL\Type\Definition\ObjectType;
21
use Digia\GraphQL\Type\Definition\TypeInterface;
22
use Psr\SimpleCache\CacheInterface;
23
use function Digia\GraphQL\Type\newSchema;
24
use function Digia\GraphQL\Util\toString;
25
26
class SchemaExtender implements SchemaExtenderInterface
27
{
28
    /**
29
     * @inheritdoc
30
     */
31
    public function extend(
32
        Schema $schema,
33
        DocumentNode $document,
34
        ?ResolverRegistryInterface $resolverRegistry = null,
35
        array $options = []
36
    ): Schema {
37
        $context = $this->createContext($schema, $document, $resolverRegistry, $options);
38
39
        // If this document contains no new types, extensions, or directives then
40
        // return the same unmodified GraphQLSchema instance.
41
        if (!$context->isSchemaExtended()) {
42
            return $schema;
43
        }
44
45
        return newSchema([
46
            'query'        => $context->getExtendedQueryType(),
47
            'mutation'     => $context->getExtendedMutationType(),
48
            'subscription' => $context->getExtendedSubscriptionType(),
49
            'types'        => $context->getExtendedTypes(),
50
            'directives'   => $context->getExtendedDirectives(),
51
            'astNode'      => $schema->getAstNode(),
52
        ]);
53
    }
54
55
    /**
56
     * @inheritdoc
57
     */
58
    protected function createContext(
59
        Schema $schema,
60
        DocumentNode $document,
61
        ?ResolverRegistryInterface $resolverRegistry,
62
        array $options
63
    ): ExtensionContextInterface {
64
        $info = $this->createInfo($schema, $document);
65
66
        // Context has to be created in order to create the definition builder,
67
        // because we are using its `resolveType` function to resolve types.
68
        $context = new ExtensionContext($info);
69
70
        $definitionBuilder = new DefinitionBuilder(
71
            $info->getTypeDefinitionMap(),
72
            $resolverRegistry,
73
            $options['types'] ?? [],
74
            $options['directives'] ?? [],
75
            [$context, 'resolveType']
76
        );
77
78
        return $context->setDefinitionBuilder($definitionBuilder);
79
    }
80
81
    /**
82
     * @param Schema       $schema
83
     * @param DocumentNode $document
84
     * @return ExtendInfo
85
     * @throws ExtensionException
86
     */
87
    protected function createInfo(Schema $schema, DocumentNode $document): ExtendInfo
88
    {
89
        $typeDefinitionMap    = [];
90
        $typeExtensionsMap    = [];
91
        $directiveDefinitions = [];
92
93
        foreach ($document->getDefinitions() as $definition) {
94
            if ($definition instanceof TypeDefinitionNodeInterface) {
95
                // Sanity check that none of the defined types conflict with the schema's existing types.
96
                $typeName     = $definition->getNameValue();
97
                $existingType = $schema->getType($typeName);
98
99
                if (null !== $existingType) {
100
                    throw new ExtensionException(
101
                        \sprintf(
102
                            'Type "%s" already exists in the schema. It cannot also ' .
103
                            'be defined in this type definition.',
104
                            $typeName
105
                        ),
106
                        [$definition]
107
                    );
108
                }
109
110
                $typeDefinitionMap[$typeName] = $definition;
111
112
                continue;
113
            }
114
115
            if ($definition instanceof ObjectTypeExtensionNode || $definition instanceof InterfaceTypeExtensionNode) {
116
                // Sanity check that this type extension exists within the schema's existing types.
117
                $extendedTypeName = $definition->getNameValue();
118
                $existingType     = $schema->getType($extendedTypeName);
119
120
                if (null === $existingType) {
121
                    throw new ExtensionException(
122
                        \sprintf(
123
                            'Cannot extend type "%s" because it does not exist in the existing schema.',
124
                            $extendedTypeName
125
                        ),
126
                        [$definition]
127
                    );
128
                }
129
130
                $this->checkExtensionNode($existingType, $definition);
131
132
                $typeExtensionsMap[$extendedTypeName] = \array_merge(
133
                    $typeExtensionsMap[$extendedTypeName] ?? [],
134
                    [$definition]
135
                );
136
137
                continue;
138
            }
139
140
            if ($definition instanceof DirectiveDefinitionNode) {
141
                $directiveName     = $definition->getNameValue();
142
                $existingDirective = $schema->getDirective($directiveName);
143
144
                if (null !== $existingDirective) {
145
                    throw new ExtensionException(
146
                        \sprintf(
147
                            'Directive "%s" already exists in the schema. It cannot be redefined.',
148
                            $directiveName
149
                        ),
150
                        [$definition]
151
                    );
152
                }
153
154
                $directiveDefinitions[] = $definition;
155
156
                continue;
157
            }
158
159
            if ($definition instanceof ScalarTypeExtensionNode ||
160
                $definition instanceof UnionTypeExtensionNode ||
161
                $definition instanceof EnumTypeExtensionNode ||
162
                $definition instanceof InputObjectTypeExtensionNode) {
163
                throw new ExtensionException(
164
                    \sprintf('The %s kind is not yet supported by extendSchema().', $definition->getKind())
165
                );
166
            }
167
        }
168
169
        return new ExtendInfo(
170
            $schema,
171
            $document,
172
            $typeDefinitionMap,
173
            $typeExtensionsMap,
174
            $directiveDefinitions
175
        );
176
    }
177
178
    /**
179
     * @param TypeInterface $type
180
     * @param NodeInterface $node
181
     * @throws ExtensionException
182
     */
183
    protected function checkExtensionNode(TypeInterface $type, NodeInterface $node): void
184
    {
185
        if ($node instanceof ObjectTypeExtensionNode && !($type instanceof ObjectType)) {
186
            throw new ExtensionException(
187
                \sprintf('Cannot extend non-object type "%s".', toString($type)),
188
                [$node]
189
            );
190
        }
191
192
        if ($node instanceof InterfaceTypeExtensionNode && !($type instanceof InterfaceType)) {
193
            throw new ExtensionException(
194
                \sprintf('Cannot extend non-interface type "%s".', toString($type)),
195
                [$node]
196
            );
197
        }
198
    }
199
}
200