simPod /
graphql-php
| 1 | <?php |
||||
| 2 | |||||
| 3 | declare(strict_types=1); |
||||
| 4 | |||||
| 5 | namespace GraphQL\Utils; |
||||
| 6 | |||||
| 7 | use GraphQL\Error\Error; |
||||
| 8 | use GraphQL\Language\AST\DirectiveDefinitionNode; |
||||
| 9 | use GraphQL\Language\AST\DocumentNode; |
||||
| 10 | use GraphQL\Language\AST\EnumTypeExtensionNode; |
||||
| 11 | use GraphQL\Language\AST\InputObjectTypeExtensionNode; |
||||
| 12 | use GraphQL\Language\AST\InterfaceTypeExtensionNode; |
||||
| 13 | use GraphQL\Language\AST\Node; |
||||
| 14 | use GraphQL\Language\AST\ObjectTypeExtensionNode; |
||||
| 15 | use GraphQL\Language\AST\SchemaDefinitionNode; |
||||
| 16 | use GraphQL\Language\AST\SchemaTypeExtensionNode; |
||||
| 17 | use GraphQL\Language\AST\TypeDefinitionNode; |
||||
| 18 | use GraphQL\Language\AST\TypeExtensionNode; |
||||
| 19 | use GraphQL\Language\AST\UnionTypeExtensionNode; |
||||
| 20 | use GraphQL\Type\Definition\CustomScalarType; |
||||
| 21 | use GraphQL\Type\Definition\Directive; |
||||
| 22 | use GraphQL\Type\Definition\EnumType; |
||||
| 23 | use GraphQL\Type\Definition\EnumValueDefinition; |
||||
| 24 | use GraphQL\Type\Definition\FieldArgument; |
||||
| 25 | use GraphQL\Type\Definition\InputObjectType; |
||||
| 26 | use GraphQL\Type\Definition\InterfaceType; |
||||
| 27 | use GraphQL\Type\Definition\ListOfType; |
||||
| 28 | use GraphQL\Type\Definition\NamedType; |
||||
| 29 | use GraphQL\Type\Definition\NonNull; |
||||
| 30 | use GraphQL\Type\Definition\ObjectType; |
||||
| 31 | use GraphQL\Type\Definition\ScalarType; |
||||
| 32 | use GraphQL\Type\Definition\Type; |
||||
| 33 | use GraphQL\Type\Definition\UnionType; |
||||
| 34 | use GraphQL\Type\Introspection; |
||||
| 35 | use GraphQL\Type\Schema; |
||||
| 36 | use GraphQL\Validator\DocumentValidator; |
||||
| 37 | use function array_keys; |
||||
| 38 | use function array_map; |
||||
| 39 | use function array_merge; |
||||
| 40 | use function array_values; |
||||
| 41 | use function count; |
||||
| 42 | |||||
| 43 | class SchemaExtender |
||||
| 44 | { |
||||
| 45 | const SCHEMA_EXTENSION = 'SchemaExtension'; |
||||
| 46 | |||||
| 47 | /** @var Type[] */ |
||||
| 48 | protected static $extendTypeCache; |
||||
| 49 | |||||
| 50 | /** @var mixed[] */ |
||||
| 51 | protected static $typeExtensionsMap; |
||||
| 52 | |||||
| 53 | /** @var ASTDefinitionBuilder */ |
||||
| 54 | protected static $astBuilder; |
||||
| 55 | |||||
| 56 | /** |
||||
| 57 | * @return TypeExtensionNode[]|null |
||||
| 58 | */ |
||||
| 59 | 60 | protected static function getExtensionASTNodes(NamedType $type) : ?array |
|||
| 60 | { |
||||
| 61 | 60 | if (! $type instanceof Type) { |
|||
| 62 | return null; |
||||
| 63 | } |
||||
| 64 | |||||
| 65 | 60 | $name = $type->name; |
|||
| 66 | 60 | if ($type->extensionASTNodes !== null) { |
|||
| 67 | 59 | if (isset(static::$typeExtensionsMap[$name])) { |
|||
| 68 | 24 | return array_merge($type->extensionASTNodes, static::$typeExtensionsMap[$name]); |
|||
| 69 | } |
||||
| 70 | |||||
| 71 | 55 | return $type->extensionASTNodes; |
|||
| 72 | } |
||||
| 73 | |||||
| 74 | 53 | return static::$typeExtensionsMap[$name] ?? null; |
|||
| 75 | } |
||||
| 76 | |||||
| 77 | /** |
||||
| 78 | * @throws Error |
||||
| 79 | */ |
||||
| 80 | 39 | protected static function checkExtensionNode(Type $type, Node $node) : void |
|||
| 81 | { |
||||
| 82 | switch (true) { |
||||
| 83 | 39 | case $node instanceof ObjectTypeExtensionNode: |
|||
| 84 | 25 | if (! ($type instanceof ObjectType)) { |
|||
| 85 | 1 | throw new Error( |
|||
| 86 | 1 | 'Cannot extend non-object type "' . $type->name . '".', |
|||
| 87 | 1 | [$node] |
|||
| 88 | ); |
||||
| 89 | } |
||||
| 90 | 24 | break; |
|||
| 91 | 23 | case $node instanceof InterfaceTypeExtensionNode: |
|||
| 92 | 10 | if (! ($type instanceof InterfaceType)) { |
|||
| 93 | 1 | throw new Error( |
|||
| 94 | 1 | 'Cannot extend non-interface type "' . $type->name . '".', |
|||
| 95 | 1 | [$node] |
|||
| 96 | ); |
||||
| 97 | } |
||||
| 98 | 9 | break; |
|||
| 99 | 17 | case $node instanceof EnumTypeExtensionNode: |
|||
| 100 | 7 | if (! ($type instanceof EnumType)) { |
|||
| 101 | 1 | throw new Error( |
|||
| 102 | 1 | 'Cannot extend non-enum type "' . $type->name . '".', |
|||
| 103 | 1 | [$node] |
|||
| 104 | ); |
||||
| 105 | } |
||||
| 106 | 6 | break; |
|||
| 107 | 13 | case $node instanceof UnionTypeExtensionNode: |
|||
| 108 | 9 | if (! ($type instanceof UnionType)) { |
|||
| 109 | 1 | throw new Error( |
|||
| 110 | 1 | 'Cannot extend non-union type "' . $type->name . '".', |
|||
| 111 | 1 | [$node] |
|||
| 112 | ); |
||||
| 113 | } |
||||
| 114 | 8 | break; |
|||
| 115 | 8 | case $node instanceof InputObjectTypeExtensionNode: |
|||
| 116 | 7 | if (! ($type instanceof InputObjectType)) { |
|||
| 117 | 1 | throw new Error( |
|||
| 118 | 1 | 'Cannot extend non-input object type "' . $type->name . '".', |
|||
| 119 | 1 | [$node] |
|||
| 120 | ); |
||||
| 121 | } |
||||
| 122 | 6 | break; |
|||
| 123 | } |
||||
| 124 | 38 | } |
|||
| 125 | |||||
| 126 | 43 | protected static function extendScalarType(ScalarType $type) : CustomScalarType |
|||
| 127 | { |
||||
| 128 | 43 | return new CustomScalarType([ |
|||
| 129 | 43 | 'name' => $type->name, |
|||
| 130 | 43 | 'description' => $type->description, |
|||
| 131 | 43 | 'astNode' => $type->astNode, |
|||
| 132 | 43 | 'serialize' => $type->config['serialize'] ?? null, |
|||
| 133 | 43 | 'parseValue' => $type->config['parseValue'] ?? null, |
|||
| 134 | 43 | 'parseLiteral' => $type->config['parseLiteral'] ?? null, |
|||
| 135 | 43 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
| 136 | ]); |
||||
| 137 | } |
||||
| 138 | |||||
| 139 | 46 | protected static function extendUnionType(UnionType $type) : UnionType |
|||
| 140 | { |
||||
| 141 | 46 | return new UnionType([ |
|||
| 142 | 46 | 'name' => $type->name, |
|||
| 143 | 46 | 'description' => $type->description, |
|||
| 144 | 'types' => static function () use ($type) { |
||||
| 145 | 45 | return static::extendPossibleTypes($type); |
|||
| 146 | 46 | }, |
|||
| 147 | 46 | 'astNode' => $type->astNode, |
|||
| 148 | 46 | 'resolveType' => $type->config['resolveType'] ?? null, |
|||
| 149 | 46 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
| 150 | ]); |
||||
| 151 | } |
||||
| 152 | |||||
| 153 | 44 | protected static function extendEnumType(EnumType $type) : EnumType |
|||
| 154 | { |
||||
| 155 | 44 | return new EnumType([ |
|||
| 156 | 44 | 'name' => $type->name, |
|||
| 157 | 44 | 'description' => $type->description, |
|||
| 158 | 44 | 'values' => static::extendValueMap($type), |
|||
| 159 | 43 | 'astNode' => $type->astNode, |
|||
| 160 | 43 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
| 161 | ]); |
||||
| 162 | } |
||||
| 163 | |||||
| 164 | 45 | protected static function extendInputObjectType(InputObjectType $type) : InputObjectType |
|||
| 165 | { |
||||
| 166 | 45 | return new InputObjectType([ |
|||
| 167 | 45 | 'name' => $type->name, |
|||
| 168 | 45 | 'description' => $type->description, |
|||
| 169 | 'fields' => static function () use ($type) { |
||||
| 170 | 45 | return static::extendInputFieldMap($type); |
|||
| 171 | 45 | }, |
|||
| 172 | 45 | 'astNode' => $type->astNode, |
|||
| 173 | 45 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
| 174 | ]); |
||||
| 175 | } |
||||
| 176 | |||||
| 177 | /** |
||||
| 178 | * @return mixed[] |
||||
| 179 | */ |
||||
| 180 | 45 | protected static function extendInputFieldMap(InputObjectType $type) : array |
|||
| 181 | { |
||||
| 182 | 45 | $newFieldMap = []; |
|||
| 183 | 45 | $oldFieldMap = $type->getFields(); |
|||
| 184 | 45 | foreach ($oldFieldMap as $fieldName => $field) { |
|||
| 185 | 44 | $newFieldMap[$fieldName] = [ |
|||
| 186 | 44 | 'description' => $field->description, |
|||
| 187 | 44 | 'type' => static::extendType($field->type), |
|||
| 188 | 44 | 'astNode' => $field->astNode, |
|||
| 189 | ]; |
||||
| 190 | |||||
| 191 | 44 | if (! $field->defaultValueExists()) { |
|||
| 192 | 44 | continue; |
|||
| 193 | } |
||||
| 194 | |||||
| 195 | $newFieldMap[$fieldName]['defaultValue'] = $field->defaultValue; |
||||
| 196 | } |
||||
| 197 | |||||
| 198 | 45 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
| 199 | 45 | if ($extensions !== null) { |
|||
| 200 | 6 | foreach ($extensions as $extension) { |
|||
| 201 | 6 | foreach ($extension->fields as $field) { |
|||
| 202 | 5 | $fieldName = $field->name->value; |
|||
| 203 | 5 | if (isset($oldFieldMap[$fieldName])) { |
|||
| 204 | 1 | throw new Error('Field "' . $type->name . '.' . $fieldName . '" already exists in the schema. It cannot also be defined in this type extension.', [$field]); |
|||
| 205 | } |
||||
| 206 | |||||
| 207 | 5 | $newFieldMap[$fieldName] = static::$astBuilder->buildInputField($field); |
|||
| 208 | } |
||||
| 209 | } |
||||
| 210 | } |
||||
| 211 | |||||
| 212 | 45 | return $newFieldMap; |
|||
| 213 | } |
||||
| 214 | |||||
| 215 | /** |
||||
| 216 | * @return mixed[] |
||||
| 217 | */ |
||||
| 218 | 44 | protected static function extendValueMap(EnumType $type) : array |
|||
| 219 | { |
||||
| 220 | 44 | $newValueMap = []; |
|||
| 221 | /** @var EnumValueDefinition[] $oldValueMap */ |
||||
| 222 | 44 | $oldValueMap = []; |
|||
| 223 | 44 | foreach ($type->getValues() as $value) { |
|||
| 224 | 43 | $oldValueMap[$value->name] = $value; |
|||
| 225 | } |
||||
| 226 | |||||
| 227 | 44 | foreach ($oldValueMap as $key => $value) { |
|||
| 228 | 43 | $newValueMap[$key] = [ |
|||
| 229 | 43 | 'name' => $value->name, |
|||
| 230 | 43 | 'description' => $value->description, |
|||
| 231 | 43 | 'value' => $value->value, |
|||
| 232 | 43 | 'deprecationReason' => $value->deprecationReason, |
|||
| 233 | 43 | 'astNode' => $value->astNode, |
|||
| 234 | ]; |
||||
| 235 | } |
||||
| 236 | |||||
| 237 | 44 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
| 238 | 44 | if ($extensions !== null) { |
|||
| 239 | 6 | foreach ($extensions as $extension) { |
|||
| 240 | 6 | foreach ($extension->values as $value) { |
|||
| 241 | 5 | $valueName = $value->name->value; |
|||
| 242 | 5 | if (isset($oldValueMap[$valueName])) { |
|||
| 243 | 1 | throw new Error('Enum value "' . $type->name . '.' . $valueName . '" already exists in the schema. It cannot also be defined in this type extension.', [$value]); |
|||
| 244 | } |
||||
| 245 | 5 | $newValueMap[$valueName] = static::$astBuilder->buildEnumValue($value); |
|||
| 246 | } |
||||
| 247 | } |
||||
| 248 | } |
||||
| 249 | |||||
| 250 | 43 | return $newValueMap; |
|||
| 251 | } |
||||
| 252 | |||||
| 253 | /** |
||||
| 254 | * @return ObjectType[] |
||||
| 255 | */ |
||||
| 256 | 45 | protected static function extendPossibleTypes(UnionType $type) : array |
|||
| 257 | { |
||||
| 258 | $possibleTypes = array_map(static function ($type) { |
||||
| 259 | 44 | return static::extendNamedType($type); |
|||
| 260 | 45 | }, $type->getTypes()); |
|||
| 261 | |||||
| 262 | 45 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
| 263 | 45 | if ($extensions !== null) { |
|||
| 264 | 8 | foreach ($extensions as $extension) { |
|||
| 265 | 8 | foreach ($extension->types as $namedType) { |
|||
| 266 | 8 | $possibleTypes[] = static::$astBuilder->buildType($namedType); |
|||
| 267 | } |
||||
| 268 | } |
||||
| 269 | } |
||||
| 270 | |||||
| 271 | 45 | return $possibleTypes; |
|||
| 272 | } |
||||
| 273 | |||||
| 274 | /** |
||||
| 275 | * @return InterfaceType[] |
||||
| 276 | */ |
||||
| 277 | 55 | protected static function extendImplementedInterfaces(ObjectType $type) : array |
|||
| 278 | { |
||||
| 279 | $interfaces = array_map(static function (InterfaceType $interfaceType) { |
||||
| 280 | 46 | return static::extendNamedType($interfaceType); |
|||
| 281 | 55 | }, $type->getInterfaces()); |
|||
| 282 | |||||
| 283 | 55 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
| 284 | 55 | if ($extensions !== null) { |
|||
| 285 | /** @var ObjectTypeExtensionNode $extension */ |
||||
| 286 | 24 | foreach ($extensions as $extension) { |
|||
| 287 | 24 | foreach ($extension->interfaces as $namedType) { |
|||
| 288 | 24 | $interfaces[] = static::$astBuilder->buildType($namedType); |
|||
| 289 | } |
||||
| 290 | } |
||||
| 291 | } |
||||
| 292 | |||||
| 293 | 55 | return $interfaces; |
|||
| 294 | } |
||||
| 295 | |||||
| 296 | 56 | protected static function extendType($typeDef) |
|||
| 297 | { |
||||
| 298 | 56 | if ($typeDef instanceof ListOfType) { |
|||
| 299 | 42 | return Type::listOf(static::extendType($typeDef->ofType)); |
|||
| 300 | } |
||||
| 301 | |||||
| 302 | 56 | if ($typeDef instanceof NonNull) { |
|||
| 303 | 56 | return Type::nonNull(static::extendType($typeDef->getWrappedType())); |
|||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 304 | } |
||||
| 305 | |||||
| 306 | 56 | return static::extendNamedType($typeDef); |
|||
| 307 | } |
||||
| 308 | |||||
| 309 | /** |
||||
| 310 | * @param FieldArgument[] $args |
||||
| 311 | * |
||||
| 312 | * @return mixed[] |
||||
| 313 | */ |
||||
| 314 | 56 | protected static function extendArgs(array $args) : array |
|||
| 315 | { |
||||
| 316 | 56 | return Utils::keyValMap( |
|||
| 317 | 56 | $args, |
|||
| 318 | static function (FieldArgument $arg) { |
||||
| 319 | 56 | return $arg->name; |
|||
| 320 | 56 | }, |
|||
| 321 | static function (FieldArgument $arg) { |
||||
| 322 | $def = [ |
||||
| 323 | 56 | 'type' => static::extendType($arg->getType()), |
|||
| 324 | 56 | 'description' => $arg->description, |
|||
| 325 | 56 | 'astNode' => $arg->astNode, |
|||
| 326 | ]; |
||||
| 327 | |||||
| 328 | 56 | if ($arg->defaultValueExists()) { |
|||
| 329 | 55 | $def['defaultValue'] = $arg->defaultValue; |
|||
| 330 | } |
||||
| 331 | |||||
| 332 | 56 | return $def; |
|||
| 333 | 56 | } |
|||
| 334 | ); |
||||
| 335 | } |
||||
| 336 | |||||
| 337 | /** |
||||
| 338 | * @param InterfaceType|ObjectType $type |
||||
| 339 | * |
||||
| 340 | * @return mixed[] |
||||
| 341 | * |
||||
| 342 | * @throws Error |
||||
| 343 | */ |
||||
| 344 | 55 | protected static function extendFieldMap($type) : array |
|||
| 345 | { |
||||
| 346 | 55 | $newFieldMap = []; |
|||
| 347 | 55 | $oldFieldMap = $type->getFields(); |
|||
| 348 | |||||
| 349 | 55 | foreach (array_keys($oldFieldMap) as $fieldName) { |
|||
| 350 | 55 | $field = $oldFieldMap[$fieldName]; |
|||
| 351 | |||||
| 352 | 55 | $newFieldMap[$fieldName] = [ |
|||
| 353 | 55 | 'name' => $fieldName, |
|||
| 354 | 55 | 'description' => $field->description, |
|||
| 355 | 55 | 'deprecationReason' => $field->deprecationReason, |
|||
| 356 | 55 | 'type' => static::extendType($field->getType()), |
|||
| 357 | 55 | 'args' => static::extendArgs($field->args), |
|||
| 358 | 55 | 'astNode' => $field->astNode, |
|||
| 359 | 55 | 'resolve' => $field->resolveFn, |
|||
| 360 | ]; |
||||
| 361 | } |
||||
| 362 | |||||
| 363 | 55 | $extensions = static::$typeExtensionsMap[$type->name] ?? null; |
|||
| 364 | 55 | if ($extensions !== null) { |
|||
| 365 | 26 | foreach ($extensions as $extension) { |
|||
| 366 | 26 | foreach ($extension->fields as $field) { |
|||
| 367 | 24 | $fieldName = $field->name->value; |
|||
| 368 | 24 | if (isset($oldFieldMap[$fieldName])) { |
|||
| 369 | 1 | throw new Error('Field "' . $type->name . '.' . $fieldName . '" already exists in the schema. It cannot also be defined in this type extension.', [$field]); |
|||
| 370 | } |
||||
| 371 | |||||
| 372 | 25 | $newFieldMap[$fieldName] = static::$astBuilder->buildField($field); |
|||
| 373 | } |
||||
| 374 | } |
||||
| 375 | } |
||||
| 376 | |||||
| 377 | 55 | return $newFieldMap; |
|||
| 378 | } |
||||
| 379 | |||||
| 380 | 59 | protected static function extendObjectType(ObjectType $type) : ObjectType |
|||
| 381 | { |
||||
| 382 | 59 | return new ObjectType([ |
|||
| 383 | 59 | 'name' => $type->name, |
|||
| 384 | 59 | 'description' => $type->description, |
|||
| 385 | 'interfaces' => static function () use ($type) { |
||||
| 386 | 55 | return static::extendImplementedInterfaces($type); |
|||
| 387 | 59 | }, |
|||
| 388 | 'fields' => static function () use ($type) { |
||||
| 389 | 55 | return static::extendFieldMap($type); |
|||
| 390 | 59 | }, |
|||
| 391 | 59 | 'astNode' => $type->astNode, |
|||
| 392 | 59 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
| 393 | 59 | 'isTypeOf' => $type->config['isTypeOf'] ?? null, |
|||
| 394 | 'resolveField' => $type->resolveFieldFn ?? null, |
||||
| 395 | ]); |
||||
| 396 | } |
||||
| 397 | 47 | ||||
| 398 | protected static function extendInterfaceType(InterfaceType $type) : InterfaceType |
||||
| 399 | 47 | { |
|||
| 400 | 47 | return new InterfaceType([ |
|||
| 401 | 47 | 'name' => $type->name, |
|||
| 402 | 'description' => $type->description, |
||||
| 403 | 46 | 'fields' => static function () use ($type) { |
|||
| 404 | 47 | return static::extendFieldMap($type); |
|||
| 405 | 47 | }, |
|||
| 406 | 47 | 'astNode' => $type->astNode, |
|||
| 407 | 47 | 'extensionASTNodes' => static::getExtensionASTNodes($type), |
|||
| 408 | 'resolveType' => $type->config['resolveType'] ?? null, |
||||
| 409 | ]); |
||||
| 410 | } |
||||
| 411 | 60 | ||||
| 412 | protected static function isSpecifiedScalarType(Type $type) : bool |
||||
| 413 | 60 | { |
|||
| 414 | return $type instanceof NamedType && |
||||
| 415 | 60 | ( |
|||
| 416 | 60 | $type->name === Type::STRING || |
|||
| 417 | 60 | $type->name === Type::INT || |
|||
| 418 | 60 | $type->name === Type::FLOAT || |
|||
| 419 | 60 | $type->name === Type::BOOLEAN || |
|||
| 420 | $type->name === Type::ID |
||||
| 421 | ); |
||||
| 422 | } |
||||
| 423 | 60 | ||||
| 424 | protected static function extendNamedType(Type $type) |
||||
| 425 | 60 | { |
|||
| 426 | 57 | if (Introspection::isIntrospectionType($type) || static::isSpecifiedScalarType($type)) { |
|||
| 427 | return $type; |
||||
| 428 | } |
||||
| 429 | 60 | ||||
| 430 | 60 | $name = $type->name; |
|||
| 431 | 60 | if (! isset(static::$extendTypeCache[$name])) { |
|||
| 432 | 43 | if ($type instanceof ScalarType) { |
|||
| 433 | 60 | static::$extendTypeCache[$name] = static::extendScalarType($type); |
|||
| 434 | 59 | } elseif ($type instanceof ObjectType) { |
|||
| 435 | 53 | static::$extendTypeCache[$name] = static::extendObjectType($type); |
|||
| 436 | 47 | } elseif ($type instanceof InterfaceType) { |
|||
| 437 | 50 | static::$extendTypeCache[$name] = static::extendInterfaceType($type); |
|||
| 438 | 46 | } elseif ($type instanceof UnionType) { |
|||
| 439 | 47 | static::$extendTypeCache[$name] = static::extendUnionType($type); |
|||
| 440 | 44 | } elseif ($type instanceof EnumType) { |
|||
| 441 | 45 | static::$extendTypeCache[$name] = static::extendEnumType($type); |
|||
| 442 | 45 | } elseif ($type instanceof InputObjectType) { |
|||
| 443 | static::$extendTypeCache[$name] = static::extendInputObjectType($type); |
||||
| 444 | } |
||||
| 445 | } |
||||
| 446 | 60 | ||||
| 447 | return static::$extendTypeCache[$name]; |
||||
| 448 | } |
||||
| 449 | |||||
| 450 | /** |
||||
| 451 | * @return mixed|null |
||||
| 452 | 60 | */ |
|||
| 453 | protected static function extendMaybeNamedType(?NamedType $type = null) |
||||
| 454 | 60 | { |
|||
| 455 | 59 | if ($type !== null) { |
|||
| 456 | return static::extendNamedType($type); |
||||
| 457 | } |
||||
| 458 | 59 | ||||
| 459 | return null; |
||||
| 460 | } |
||||
| 461 | |||||
| 462 | /** |
||||
| 463 | * @param DirectiveDefinitionNode[] $directiveDefinitions |
||||
| 464 | * |
||||
| 465 | * @return Directive[] |
||||
| 466 | 56 | */ |
|||
| 467 | protected static function getMergedDirectives(Schema $schema, array $directiveDefinitions) : array |
||||
| 468 | { |
||||
| 469 | 56 | $existingDirectives = array_map(static function (Directive $directive) { |
|||
| 470 | 56 | return static::extendDirective($directive); |
|||
| 471 | }, $schema->getDirectives()); |
||||
| 472 | 56 | ||||
| 473 | Utils::invariant(count($existingDirectives) > 0, 'schema must have default directives'); |
||||
| 474 | 56 | ||||
| 475 | 56 | return array_merge( |
|||
| 476 | $existingDirectives, |
||||
| 477 | 9 | array_map(static function (DirectiveDefinitionNode $directive) { |
|||
| 478 | 56 | return static::$astBuilder->buildDirective($directive); |
|||
| 479 | }, $directiveDefinitions) |
||||
| 480 | ); |
||||
| 481 | } |
||||
| 482 | 56 | ||||
| 483 | protected static function extendDirective(Directive $directive) : Directive |
||||
| 484 | 56 | { |
|||
| 485 | 56 | return new Directive([ |
|||
| 486 | 56 | 'name' => $directive->name, |
|||
| 487 | 56 | 'description' => $directive->description, |
|||
| 488 | 56 | 'locations' => $directive->locations, |
|||
| 489 | 56 | 'args' => static::extendArgs($directive->args), |
|||
| 490 | 'astNode' => $directive->astNode, |
||||
| 491 | ]); |
||||
| 492 | } |
||||
| 493 | |||||
| 494 | /** |
||||
| 495 | * @param mixed[]|null $options |
||||
| 496 | 66 | */ |
|||
| 497 | public static function extend(Schema $schema, DocumentNode $documentAST, ?array $options = null) : Schema |
||||
| 498 | 66 | { |
|||
| 499 | 64 | if ($options === null || ! (isset($options['assumeValid']) || isset($options['assumeValidSDL']))) { |
|||
| 500 | DocumentValidator::assertValidSDLExtension($documentAST, $schema); |
||||
| 501 | } |
||||
| 502 | 65 | ||||
| 503 | 65 | $typeDefinitionMap = []; |
|||
| 504 | 65 | static::$typeExtensionsMap = []; |
|||
| 505 | $directiveDefinitions = []; |
||||
| 506 | 65 | /** @var SchemaDefinitionNode|null $schemaDef */ |
|||
| 507 | $schemaDef = null; |
||||
| 508 | 65 | /** @var SchemaTypeExtensionNode[] $schemaExtensions */ |
|||
| 509 | $schemaExtensions = []; |
||||
| 510 | 65 | ||||
| 511 | 65 | $definitionsCount = count($documentAST->definitions); |
|||
| 512 | for ($i = 0; $i < $definitionsCount; $i++) { |
||||
| 513 | |||||
| 514 | 65 | /** @var Node $def */ |
|||
| 515 | $def = $documentAST->definitions[$i]; |
||||
| 516 | 65 | ||||
| 517 | 1 | if ($def instanceof SchemaDefinitionNode) { |
|||
| 518 | 64 | $schemaDef = $def; |
|||
| 519 | 9 | } elseif ($def instanceof SchemaTypeExtensionNode) { |
|||
| 520 | 62 | $schemaExtensions[] = $def; |
|||
| 521 | 21 | } elseif ($def instanceof TypeDefinitionNode) { |
|||
| 522 | $typeName = isset($def->name) ? $def->name->value : null; |
||||
| 523 | |||||
| 524 | 21 | try { |
|||
| 525 | 2 | $type = $schema->getType($typeName); |
|||
|
0 ignored issues
–
show
It seems like
$typeName can also be of type null; however, parameter $name of GraphQL\Type\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...
|
|||||
| 526 | 2 | } catch (Error $error) { |
|||
| 527 | $type = null; |
||||
| 528 | } |
||||
| 529 | 21 | ||||
| 530 | 1 | if ($type) { |
|||
| 531 | throw new Error('Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.', [$def]); |
||||
| 532 | 20 | } |
|||
| 533 | 47 | $typeDefinitionMap[$typeName] = $def; |
|||
| 534 | 40 | } elseif ($def instanceof TypeExtensionNode) { |
|||
| 535 | 40 | $extendedTypeName = isset($def->name) ? $def->name->value : null; |
|||
| 536 | 40 | $existingType = $schema->getType($extendedTypeName); |
|||
| 537 | 1 | if ($existingType === null) { |
|||
| 538 | throw new Error('Cannot extend type "' . $extendedTypeName . '" because it does not exist in the existing schema.', [$def]); |
||||
| 539 | } |
||||
| 540 | 39 | ||||
| 541 | static::checkExtensionNode($existingType, $def); |
||||
| 542 | 38 | ||||
| 543 | 38 | $existingTypeExtensions = static::$typeExtensionsMap[$extendedTypeName] ?? null; |
|||
| 544 | 11 | static::$typeExtensionsMap[$extendedTypeName] = $existingTypeExtensions !== null ? array_merge($existingTypeExtensions, [$def]) : [$def]; |
|||
| 545 | 10 | } elseif ($def instanceof DirectiveDefinitionNode) { |
|||
| 546 | 10 | $directiveName = $def->name->value; |
|||
| 547 | 10 | $existingDirective = $schema->getDirective($directiveName); |
|||
| 548 | 2 | if ($existingDirective !== null) { |
|||
| 549 | throw new Error('Directive "' . $directiveName . '" already exists in the schema. It cannot be redefined.', [$def]); |
||||
| 550 | 9 | } |
|||
| 551 | $directiveDefinitions[] = $def; |
||||
| 552 | } |
||||
| 553 | } |
||||
| 554 | 61 | ||||
| 555 | 61 | if (count(static::$typeExtensionsMap) === 0 && |
|||
| 556 | 61 | count($typeDefinitionMap) === 0 && |
|||
| 557 | 61 | count($directiveDefinitions) === 0 && |
|||
| 558 | 61 | count($schemaExtensions) === 0 && |
|||
| 559 | $schemaDef === null |
||||
| 560 | 1 | ) { |
|||
| 561 | return $schema; |
||||
| 562 | } |
||||
| 563 | 60 | ||||
| 564 | 60 | static::$astBuilder = new ASTDefinitionBuilder( |
|||
| 565 | 60 | $typeDefinitionMap, |
|||
| 566 | $options, |
||||
| 567 | static function (string $typeName) use ($schema) { |
||||
| 568 | 12 | /** @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|InputObjectType $existingType */ |
|||
| 569 | 12 | $existingType = $schema->getType($typeName); |
|||
| 570 | 11 | if ($existingType !== null) { |
|||
| 571 | return static::extendNamedType($existingType); |
||||
| 572 | } |
||||
| 573 | 1 | ||||
| 574 | 60 | throw new Error('Unknown type: "' . $typeName . '". Ensure that this type exists either in the original schema, or is added in a type definition.', [$typeName]); |
|||
| 575 | } |
||||
| 576 | ); |
||||
| 577 | 60 | ||||
| 578 | static::$extendTypeCache = []; |
||||
| 579 | |||||
| 580 | 60 | $operationTypes = [ |
|||
| 581 | 60 | 'query' => static::extendMaybeNamedType($schema->getQueryType()), |
|||
| 582 | 60 | 'mutation' => static::extendMaybeNamedType($schema->getMutationType()), |
|||
| 583 | 'subscription' => static::extendMaybeNamedType($schema->getSubscriptionType()), |
||||
| 584 | ]; |
||||
| 585 | 60 | ||||
| 586 | 1 | if ($schemaDef) { |
|||
| 587 | 1 | foreach ($schemaDef->operationTypes as $operationType) { |
|||
| 588 | 1 | $operation = $operationType->operation; |
|||
| 589 | $type = $operationType->type; |
||||
| 590 | 1 | ||||
| 591 | if (isset($operationTypes[$operation])) { |
||||
| 592 | throw new Error('Must provide only one ' . $operation . ' type in schema.'); |
||||
| 593 | } |
||||
| 594 | 1 | ||||
| 595 | $operationTypes[$operation] = static::$astBuilder->buildType($type); |
||||
| 596 | } |
||||
| 597 | } |
||||
| 598 | 60 | ||||
| 599 | 9 | foreach ($schemaExtensions as $schemaExtension) { |
|||
| 600 | 2 | if (! $schemaExtension->operationTypes) { |
|||
| 601 | continue; |
||||
| 602 | } |
||||
| 603 | 8 | ||||
| 604 | 8 | foreach ($schemaExtension->operationTypes as $operationType) { |
|||
| 605 | 8 | $operation = $operationType->operation; |
|||
| 606 | 3 | if (isset($operationTypes[$operation])) { |
|||
| 607 | throw new Error('Must provide only one ' . $operation . ' type in schema.'); |
||||
| 608 | 7 | } |
|||
| 609 | $operationTypes[$operation] = static::$astBuilder->buildType($operationType->type); |
||||
| 610 | } |
||||
| 611 | } |
||||
| 612 | 57 | ||||
| 613 | 6 | $schemaExtensionASTNodes = array_merge($schema->extensionASTNodes, $schemaExtensions); |
|||
| 614 | 2 | ||||
| 615 | 6 | $types = array_merge( |
|||
| 616 | 57 | // Iterate through all types, getting the type definition for each, ensuring |
|||
| 617 | // that any type not directly referenced by a field will get created. |
||||
| 618 | 57 | array_map(static function ($type) { |
|||
| 619 | return static::extendNamedType($type); |
||||
| 620 | }, array_values($schema->getTypeMap())), |
||||
| 621 | // Do the same with new types. |
||||
| 622 | 57 | array_map(static function ($type) { |
|||
| 623 | 57 | return static::$astBuilder->buildType($type); |
|||
| 624 | }, array_values($typeDefinitionMap)) |
||||
| 625 | ); |
||||
| 626 | 17 | ||||
| 627 | 56 | return new Schema([ |
|||
| 628 | 'query' => $operationTypes['query'], |
||||
| 629 | 'mutation' => $operationTypes['mutation'], |
||||
| 630 | 56 | 'subscription' => $operationTypes['subscription'], |
|||
| 631 | 56 | 'types' => $types, |
|||
| 632 | 56 | 'directives' => static::getMergedDirectives($schema, $directiveDefinitions), |
|||
| 633 | 56 | 'astNode' => $schema->getAstNode(), |
|||
| 634 | 56 | 'extensionASTNodes' => $schemaExtensionASTNodes, |
|||
| 635 | 56 | ]); |
|||
| 636 | 56 | } |
|||
| 637 | } |
||||
| 638 |