Passed
Pull Request — master (#141)
by Christoffer
02:17
created

IntrospectionProvider   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 461
Duplicated Lines 0 %

Importance

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

3 Methods

Rating   Name   Duplication   Size   Complexity  
B registerMetaFields() 0 39 2
A register() 0 4 1
D registerIntrospectionTypes() 0 384 18
1
<?php
2
3
namespace Digia\GraphQL\Type;
4
5
use Digia\GraphQL\Error\InvalidTypeException;
6
use Digia\GraphQL\Execution\Resolver\ResolveInfo;
7
use Digia\GraphQL\GraphQL;
8
use Digia\GraphQL\Language\DirectiveLocationEnum;
9
use Digia\GraphQL\Type\Definition\AbstractTypeInterface;
10
use Digia\GraphQL\Type\Definition\ArgumentsAwareInterface;
11
use Digia\GraphQL\Type\Definition\DirectiveInterface;
12
use Digia\GraphQL\Type\Definition\EnumType;
13
use Digia\GraphQL\Type\Definition\Field;
14
use Digia\GraphQL\Type\Definition\InputObjectType;
15
use Digia\GraphQL\Type\Definition\InterfaceType;
16
use Digia\GraphQL\Type\Definition\ListType;
17
use Digia\GraphQL\Type\Definition\NonNullType;
18
use Digia\GraphQL\Type\Definition\ObjectType;
19
use Digia\GraphQL\Type\Definition\ScalarType;
20
use Digia\GraphQL\Type\Definition\TypeInterface;
21
use Digia\GraphQL\Type\Definition\UnionType;
22
use League\Container\ServiceProvider\AbstractServiceProvider;
23
24
class IntrospectionProvider extends AbstractServiceProvider
25
{
26
    /**
27
     * @var array
28
     */
29
    protected $provides = [
30
        // Introspection types
31
        GraphQL::SCHEMA_INTROSPECTION,
32
        GraphQL::DIRECTIVE_INTROSPECTION,
33
        GraphQL::DIRECTIVE_LOCATION_INTROSPECTION,
34
        GraphQL::TYPE_INTROSPECTION,
35
        GraphQL::FIELD_INTROSPECTION,
36
        GraphQL::INPUT_VALUE_INTROSPECTION,
37
        GraphQL::ENUM_VALUE_INTROSPECTION,
38
        GraphQL::TYPE_KIND_INTROSPECTION,
39
        // Meta fields
40
        GraphQL::SCHEMA_META_FIELD_DEFINITION,
41
        GraphQL::TYPE_META_FIELD_DEFINITION,
42
        GraphQL::TYPE_NAME_META_FIELD_DEFINITION,
43
    ];
44
45
    /**
46
     * @inheritdoc
47
     */
48
    public function register()
49
    {
50
        $this->registerIntrospectionTypes();
51
        $this->registerMetaFields();
52
    }
53
54
    /**
55
     * Registers the introspection types with the container.
56
     */
57
    protected function registerIntrospectionTypes()
58
    {
59
        $this->container->add(GraphQL::SCHEMA_INTROSPECTION, function () {
60
            return GraphQLObjectType([
61
                'name'            => GraphQL::SCHEMA_INTROSPECTION,
62
                'isIntrospection' => true,
63
                'description'     =>
64
                    'A GraphQL Schema defines the capabilities of a GraphQL server. It ' .
65
                    'exposes all available types and directives on the server, as well as ' .
66
                    'the entry points for query, mutation, and subscription operations.',
67
                'fields'          => function () {
68
                    return [
69
                        'types'            => [
70
                            'description' => 'A list of all types supported by this server.',
71
                            'type'        => GraphQLNonNull(GraphQLList(GraphQLNonNull(__Type()))),
72
                            'resolve'     => function (SchemaInterface $schema): array {
73
                                return array_values($schema->getTypeMap());
74
                            },
75
                        ],
76
                        'queryType'        => [
77
                            'description' => 'The type that query operations will be rooted at.',
78
                            'type'        => GraphQLNonNull(__Type()),
79
                            'resolve'     => function (SchemaInterface $schema): ?TypeInterface {
80
                                return $schema->getQueryType();
81
                            },
82
                        ],
83
                        'mutationType'     => [
84
                            'description' =>
85
                                'If this server supports mutation, the type that ' .
86
                                'mutation operations will be rooted at.',
87
                            'type'        => __Type(),
88
                            'resolve'     => function (SchemaInterface $schema): ?TypeInterface {
89
                                return $schema->getMutationType();
90
                            },
91
                        ],
92
                        'subscriptionType' => [
93
                            'description' =>
94
                                'If this server support subscription, the type that ' .
95
                                'subscription operations will be rooted at.',
96
                            'type'        => __Type(),
97
                            'resolve'     => function (SchemaInterface $schema): ?TypeInterface {
98
                                return $schema->getSubscriptionType();
99
                            },
100
                        ],
101
                        'directives'       => [
102
                            'description' => 'A list of all directives supported by this server.',
103
                            'type'        => GraphQLNonNull(GraphQLList(GraphQLNonNull(__Directive()))),
104
                            'resolve'     => function (SchemaInterface $schema): array {
105
                                return $schema->getDirectives();
106
                            },
107
                        ],
108
                    ];
109
                }
110
            ]);
111
        }, true/* $shared */);
112
113
        $this->container->add(GraphQL::DIRECTIVE_INTROSPECTION, function () {
114
            return GraphQLObjectType([
115
                'name'            => GraphQL::DIRECTIVE_INTROSPECTION,
116
                'isIntrospection' => true,
117
                'description'     =>
118
                    'A Directive provides a way to describe alternate runtime execution and ' .
119
                    'type validation behavior in a GraphQL document.' .
120
                    "\n\nIn some cases, you need to provide options to alter GraphQL's " .
121
                    'execution behavior in ways field arguments will not suffice, such as ' .
122
                    'conditionally including or skipping a field. Directives provide this by ' .
123
                    'describing additional information to the executor.',
124
                'fields'          => function () {
125
                    return [
126
                        'name'        => ['type' => GraphQLNonNull(GraphQLString())],
127
                        'description' => ['type' => GraphQLString()],
128
                        'locations'   => [
129
                            'type' => GraphQLNonNull(GraphQLList(GraphQLNonNull(__DirectiveLocation()))),
130
                        ],
131
                        'args'        => [
132
                            'type'    => GraphQLNonNull(GraphQLList(GraphQLNonNull(__InputValue()))),
133
                            'resolve' => function (DirectiveInterface $directive): array {
134
                                return $directive->getArguments() ?: [];
135
                            },
136
                        ],
137
                    ];
138
                }
139
            ]);
140
        }, true/* $shared */);
141
142
        $this->container->add(GraphQL::DIRECTIVE_LOCATION_INTROSPECTION, function () {
143
            return GraphQLEnumType([
144
                'name'            => GraphQL::DIRECTIVE_LOCATION_INTROSPECTION,
145
                'isIntrospection' => true,
146
                'description'     =>
147
                    'A Directive can be adjacent to many parts of the GraphQL language, a ' .
148
                    '__DirectiveLocation describes one such possible adjacencies.',
149
                'values'          => [
150
                    DirectiveLocationEnum::QUERY                  => [
151
                        'description' => 'Location adjacent to a query operation.',
152
                    ],
153
                    DirectiveLocationEnum::MUTATION               => [
154
                        'description' => 'Location adjacent to a mutation operation.',
155
                    ],
156
                    DirectiveLocationEnum::SUBSCRIPTION           => [
157
                        'description' => 'Location adjacent to a subscription operation.',
158
                    ],
159
                    DirectiveLocationEnum::FIELD                  => [
160
                        'description' => 'Location adjacent to a field.',
161
                    ],
162
                    DirectiveLocationEnum::FRAGMENT_DEFINITION    => [
163
                        'description' => 'Location adjacent to a fragment definition.',
164
                    ],
165
                    DirectiveLocationEnum::FRAGMENT_SPREAD        => [
166
                        'description' => 'Location adjacent to a fragment spread.',
167
                    ],
168
                    DirectiveLocationEnum::INLINE_FRAGMENT        => [
169
                        'description' => 'Location adjacent to an inline fragment.',
170
                    ],
171
                    DirectiveLocationEnum::SCHEMA                 => [
172
                        'description' => 'Location adjacent to a schema definition.',
173
                    ],
174
                    DirectiveLocationEnum::SCALAR                 => [
175
                        'description' => 'Location adjacent to a scalar definition.',
176
                    ],
177
                    DirectiveLocationEnum::OBJECT                 => [
178
                        'description' => 'Location adjacent to an object type definition.',
179
                    ],
180
                    DirectiveLocationEnum::FIELD_DEFINITION       => [
181
                        'description' => 'Location adjacent to a field definition.',
182
                    ],
183
                    DirectiveLocationEnum::ARGUMENT_DEFINITION    => [
184
                        'description' => 'Location adjacent to an argument definition.',
185
                    ],
186
                    DirectiveLocationEnum::INTERFACE              => [
187
                        'description' => 'Location adjacent to an interface definition.',
188
                    ],
189
                    DirectiveLocationEnum::UNION                  => [
190
                        'description' => 'Location adjacent to a union definition.',
191
                    ],
192
                    DirectiveLocationEnum::ENUM                   => [
193
                        'description' => 'Location adjacent to an enum definition.',
194
                    ],
195
                    DirectiveLocationEnum::ENUM_VALUE             => [
196
                        'description' => 'Location adjacent to an enum value definition.',
197
                    ],
198
                    DirectiveLocationEnum::INPUT_OBJECT           => [
199
                        'description' => 'Location adjacent to an input object type definition.',
200
                    ],
201
                    DirectiveLocationEnum::INPUT_FIELD_DEFINITION => [
202
                        'description' => 'Location adjacent to an input object field definition.',
203
                    ],
204
                ],
205
            ]);
206
        }, true/* $shared */);
207
208
        $this->container->add(GraphQL::TYPE_INTROSPECTION, function () {
209
            return GraphQLObjectType([
210
                'name'            => GraphQL::TYPE_INTROSPECTION,
211
                'isIntrospection' => true,
212
                'description'     =>
213
                    'The fundamental unit of any GraphQL Schema is the type. There are ' .
214
                    'many kinds of types in GraphQL as represented by the `__TypeKind` enum.' .
215
                    '\n\nDepending on the kind of a type, certain fields describe ' .
216
                    'information about that type. Scalar types provide no information ' .
217
                    'beyond a name and description, while Enum types provide their values. ' .
218
                    'Object and Interface types provide the fields they describe. Abstract ' .
219
                    'types, Union and Interface, provide the Object types possible ' .
220
                    'at runtime. List and NonNull types compose other types.',
221
                'fields'          => function () {
222
                    return [
223
                        'kind'          => [
224
                            'type'    => GraphQLNonNull(__TypeKind()),
225
                            'resolve' => function (TypeInterface $type) {
226
                                if ($type instanceof ScalarType) {
227
                                    return TypeKindEnum::SCALAR;
228
                                }
229
                                if ($type instanceof ObjectType) {
230
                                    return TypeKindEnum::OBJECT;
231
                                }
232
                                if ($type instanceof InterfaceType) {
233
                                    return TypeKindEnum::INTERFACE;
234
                                }
235
                                if ($type instanceof UnionType) {
236
                                    return TypeKindEnum::UNION;
237
                                }
238
                                if ($type instanceof EnumType) {
239
                                    return TypeKindEnum::ENUM;
240
                                }
241
                                if ($type instanceof InputObjectType) {
242
                                    return TypeKindEnum::INPUT_OBJECT;
243
                                }
244
                                if ($type instanceof ListType) {
245
                                    return TypeKindEnum::LIST;
246
                                }
247
                                if ($type instanceof NonNullType) {
248
                                    return TypeKindEnum::NON_NULL;
249
                                }
250
251
                                throw new InvalidTypeException(sprintf('Unknown kind of type: %s', $type));
0 ignored issues
show
Bug introduced by
$type of type Digia\GraphQL\Type\Definition\TypeInterface is incompatible with the type string expected by parameter $args of sprintf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

251
                                throw new InvalidTypeException(sprintf('Unknown kind of type: %s', /** @scrutinizer ignore-type */ $type));
Loading history...
252
                            },
253
                        ],
254
                        'name'          => ['type' => GraphQLString()],
255
                        'description'   => ['type' => GraphQLString()],
256
                        'fields'        => [
257
                            'type'    => GraphQLList(GraphQLNonNull(__Field())),
258
                            'args'    => [
259
                                'includeDeprecated' => ['type' => GraphQLBoolean(), 'defaultValue' => false],
260
                            ],
261
                            'resolve' => function (TypeInterface $type, array $args):
262
                            ?array {
263
                                $includeDeprecated = $args[0] ?? null;
264
265
                                if ($type instanceof ObjectType || $type instanceof InterfaceType) {
266
                                    $fields = array_values($type->getFields());
267
268
                                    if (!$includeDeprecated) {
269
                                        $fields = array_filter($fields, function (Field $field) {
270
                                            return !$field->getIsDeprecated();
271
                                        });
272
                                    }
273
274
                                    return $fields;
275
                                }
276
277
                                return null;
278
                            },
279
                        ],
280
                        'interfaces'    => [
281
                            'type'    => GraphQLList(GraphQLNonNull(__Type())),
282
                            'resolve' => function (TypeInterface $type): ?array {
283
                                return $type instanceof ObjectType ? $type->getInterfaces() : null;
284
                            },
285
                        ],
286
                        'possibleTypes' => [
287
                            'type'    => GraphQLList(GraphQLNonNull(__Type())),
288
                            'resolve' => function (
289
                                TypeInterface $type,
290
                                array $args,
291
                                array $context,
292
                                ResolveInfo $info
293
                            ):
294
                            ?array {
295
                                /** @var SchemaInterface $schema */
296
                                $schema = $info->getSchema();
297
                                /** @noinspection PhpParamsInspection */
298
                                return $type instanceof AbstractTypeInterface ? $schema->getPossibleTypes($type) : null;
299
                            },
300
                        ],
301
                        'enumValues'    => [
302
                            'type'    => GraphQLList(GraphQLNonNull(__EnumValue())),
303
                            'args'    => [
304
                                'includeDeprecated' => ['type' => GraphQLBoolean(), 'defaultValue' => false],
305
                            ],
306
                            'resolve' => function (TypeInterface $type, array $args): ?array {
307
                                [$includeDeprecated] = $args;
308
309
                                if ($type instanceof EnumType) {
310
                                    $values = array_values($type->getValues());
311
312
                                    if (!$includeDeprecated) {
313
                                        $values = array_filter($values, function (Field $field) {
314
                                            return !$field->getIsDeprecated();
315
                                        });
316
                                    }
317
318
                                    return $values;
319
                                }
320
321
                                return null;
322
                            },
323
                        ],
324
                        'inputFields'   => [
325
                            'type'    => GraphQLList(GraphQLNonNull(__InputValue())),
326
                            'resolve' => function (TypeInterface $type): ?array {
327
                                return $type instanceof InputObjectType ? $type->getFields() : null;
328
                            },
329
                        ],
330
                        'ofType'        => ['type' => __Type()],
331
                    ];
332
                }
333
            ]);
334
        }, true/* $shared */);
335
336
        $this->container->add(GraphQL::FIELD_INTROSPECTION, function () {
337
            return GraphQLObjectType([
338
                'name'            => GraphQL::FIELD_INTROSPECTION,
339
                'isIntrospection' => true,
340
                'description'     =>
341
                    'Object and Interface types are described by a list of Fields, each of ' .
342
                    'which has a name, potentially a list of arguments, and a return type.',
343
                'fields'          => function () {
344
                    return [
345
                        'name'              => ['type' => GraphQLNonNull(GraphQLString())],
346
                        'description'       => ['type' => GraphQLString()],
347
                        'args'              => [
348
                            'type'    => GraphQLNonNull(GraphQLList(GraphQLNonNull(__InputValue()))),
349
                            'resolve' => function (ArgumentsAwareInterface $directive): array {
350
                                return $directive->getArguments() ?? [];
351
                            },
352
                        ],
353
                        'type'              => ['type' => GraphQLNonNull(__Type())],
354
                        'isDeprecated'      => ['type' => GraphQLNonNull(GraphQLBoolean())],
355
                        'deprecationReason' => ['type' => GraphQLString()],
356
                    ];
357
                }
358
            ]);
359
        }, true/* $shared */);
360
361
        $this->container->add(GraphQL::INPUT_VALUE_INTROSPECTION, function () {
362
            return GraphQLObjectType([
363
                'name'            => GraphQL::INPUT_VALUE_INTROSPECTION,
364
                'isIntrospection' => true,
365
                'description'     =>
366
                    'Arguments provided to Fields or Directives and the input fields of an ' .
367
                    'InputObject are represented as Input Values which describe their type ' .
368
                    'and optionally a default value.',
369
                'fields'          => function () {
370
                    return [
371
                        'name'         => ['type' => GraphQLNonNull(GraphQLString())],
372
                        'description'  => ['type' => GraphQLString()],
373
                        'type'         => ['type' => GraphQLNonNull(__Type())],
374
                        'defaultValue' => [
375
                            'type'        => GraphQLString(),
376
                            'description' =>
377
                                'A GraphQL-formatted string representing the default value for this ' .
378
                                'input value.',
379
                            'resolve'     => function ($inputValue) {
0 ignored issues
show
Unused Code introduced by
The parameter $inputValue is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

379
                            'resolve'     => function (/** @scrutinizer ignore-unused */ $inputValue) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
380
                                // TODO: Implement this when we have support for printing AST.
381
                                return null;
382
                            }
383
                        ],
384
                    ];
385
                }
386
            ]);
387
        }, true/* $shared */);
388
389
        $this->container->add(GraphQL::ENUM_VALUE_INTROSPECTION, function () {
390
            return GraphQLObjectType([
391
                'name'            => GraphQL::ENUM_VALUE_INTROSPECTION,
392
                'isIntrospection' => true,
393
                'description'     =>
394
                    'One possible value for a given Enum. Enum values are unique values, not ' .
395
                    'a placeholder for a string or numeric value. However an Enum value is ' .
396
                    'returned in a JSON response as a string.',
397
                'fields'          => function () {
398
                    return [
399
                        'name'              => ['type' => GraphQLNonNull(GraphQLString())],
400
                        'description'       => ['type' => GraphQLString()],
401
                        'isDeprecated'      => ['type' => GraphQLNonNull(GraphQLBoolean())],
402
                        'deprecationReason' => ['type' => GraphQLString()],
403
                    ];
404
                }
405
            ]);
406
        }, true/* $shared */);
407
408
        $this->container->add(GraphQL::TYPE_KIND_INTROSPECTION, function () {
409
            return GraphQLEnumType([
410
                'name'            => GraphQL::TYPE_KIND_INTROSPECTION,
411
                'isIntrospection' => true,
412
                'description'     => 'An enum describing what kind of type a given `__Type` is.',
413
                'values'          => [
414
                    TypeKindEnum::SCALAR       => [
415
                        'description' => 'Indicates this type is a scalar.',
416
                    ],
417
                    TypeKindEnum::OBJECT       => [
418
                        'description' => 'Indicates this type is an object. `fields` and `interfaces` are valid fields.',
419
                    ],
420
                    TypeKindEnum::INTERFACE    => [
421
                        'description' => 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.',
422
                    ],
423
                    TypeKindEnum::UNION        => [
424
                        'description' => 'Indicates this type is a union. `possibleTypes` is a valid field.',
425
                    ],
426
                    TypeKindEnum::ENUM         => [
427
                        'description' => 'Indicates this type is an enum. `enumValues` is a valid field.',
428
                    ],
429
                    TypeKindEnum::INPUT_OBJECT => [
430
                        'description' => 'Indicates this type is an input object. `inputFields` is a valid field.',
431
                    ],
432
                    TypeKindEnum::LIST         => [
433
                        'description' => 'Indicates this type is a list. `ofType` is a valid field.',
434
                    ],
435
                    TypeKindEnum::NON_NULL     => [
436
                        'description' => 'Indicates this type is a non-null. `ofType` is a valid field.',
437
                    ],
438
                ],
439
            ]);
440
        }, true/* $shared */);
441
    }
442
443
    /**
444
     * Registers the introspection meta fields with the container.
445
     */
446
    protected function registerMetaFields()
447
    {
448
        $this->container->add(GraphQL::SCHEMA_META_FIELD_DEFINITION, function ($__Schema) {
449
            return new Field([
450
                'name'        => '__schema',
451
                'type'        => GraphQLNonNull($__Schema),
452
                'description' => 'Access the current type schema of this server.',
453
                'resolve'     => function ($source, $args, $context, ResolveInfo $info): SchemaInterface {
454
                    return $info->getSchema();
455
                }
456
            ]);
457
        })
458
            ->withArgument(GraphQL::SCHEMA_INTROSPECTION);
459
460
        $this->container->add(GraphQL::TYPE_META_FIELD_DEFINITION, function ($__Type) {
461
            return new Field([
462
                'name'        => '__type',
463
                'type'        => $__Type,
464
                'description' => 'Request the type information of a single type.',
465
                'args'        => [
466
                    'name' => ['type' => GraphQLNonNull(GraphQLString())],
467
                ],
468
                'resolve'     => function ($source, $args, $context, ResolveInfo $info): TypeInterface {
469
                    ['name' => $name] = $args;
470
                    $schema = $info->getSchema();
471
                    return $schema->getType($name);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $schema->getType($name) could return the type null which is incompatible with the type-hinted return Digia\GraphQL\Type\Definition\TypeInterface. Consider adding an additional type-check to rule them out.
Loading history...
472
                }
473
            ]);
474
        })
475
            ->withArgument(GraphQL::TYPE_INTROSPECTION);
476
477
        $this->container->add(GraphQL::TYPE_NAME_META_FIELD_DEFINITION, function () {
478
            return new Field([
479
                'name'        => '__typename',
480
                'type'        => GraphQLNonNull(GraphQLString()),
481
                'description' => 'The name of the current Object type at runtime.',
482
                'resolve'     => function ($source, $args, $context, ResolveInfo $info): string {
483
                    $parentType = $info->getParentType();
484
                    return null !== $parentType ? $parentType->getName() : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return null !== $parentT...tType->getName() : null could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
485
                }
486
            ]);
487
        });
488
    }
489
}
490