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