Completed
Pull Request — master (#15)
by Christoffer
03:18 queued 01:12
created

SchemaMetaFieldDefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 0
1
<?php
2
3
namespace Digia\GraphQL\Type;
4
5
use Digia\GraphQL\Language\AST\DirectiveLocationEnum;
6
use Digia\GraphQL\Type\Contract\SchemaInterface;
7
use Digia\GraphQL\Type\Definition\AbstractType;
8
use Digia\GraphQL\Type\Definition\Contract\DirectiveInterface;
9
use Digia\GraphQL\Type\Definition\Contract\TypeInterface;
10
use Digia\GraphQL\Type\Definition\EnumType;
11
use Digia\GraphQL\Type\Definition\Field;
12
use Digia\GraphQL\Type\Definition\InputObjectType;
13
use Digia\GraphQL\Type\Definition\InterfaceType;
14
use Digia\GraphQL\Type\Definition\ListType;
15
use Digia\GraphQL\Type\Definition\NonNullType;
16
use Digia\GraphQL\Type\Definition\ObjectType;
17
use Digia\GraphQL\Type\Definition\ScalarType;
18
use Digia\GraphQL\Type\Definition\UnionType;
19
use function Digia\GraphQL\Util\arraySome;
20
21
/**
22
 * @return ObjectType
23
 */
24
function __Schema(): ObjectType
25
{
26
    static $instance = null;
27
28
    if (null === $instance) {
29
        $instance = GraphQLObjectType([
30
            'name'            => '__Schema',
31
            'isIntrospection' => true,
32
            'description'     =>
33
                'A GraphQL Schema defines the capabilities of a GraphQL server. It ' .
34
                'exposes all available types and directives on the server, as well as ' .
35
                'the entry points for query, mutation, and subscription operations.',
36
            'fields'          => function () {
37
                return [
38
                    'types'            => [
39
                        'description' => 'A list of all types supported by this server.',
40
                        'type'        => GraphQLNonNull(GraphQLList(GraphQLNonNull(__Type()))),
41
                        'resolve'     => function (SchemaInterface $schema): array {
42
                            return array_values($schema->getTypeMap());
43
                        },
44
                    ],
45
                    'queryType'        => [
46
                        'description' => 'The type that query operations will be rooted at.',
47
                        'type'        => GraphQLNonNull(__Type()),
48
                        'resolve'     => function (SchemaInterface $schema): ObjectType {
49
                            return $schema->getQuery();
50
                        },
51
                    ],
52
                    'mutationType'     => [
53
                        'description' =>
54
                            'If this server supports mutation, the type that ' .
55
                            'mutation operations will be rooted at.',
56
                        'type'        => __Type(),
57
                        'resolve'     => function (SchemaInterface $schema): ObjectType {
58
                            return $schema->getMutation();
59
                        },
60
                    ],
61
                    'subscriptionType' => [
62
                        'description' =>
63
                            'If this server support subscription, the type that ' .
64
                            'subscription operations will be rooted at.',
65
                        'type'        => __Type(),
66
                        'resolve'     => function (SchemaInterface $schema): ObjectType {
67
                            return $schema->getSubscription();
68
                        },
69
                    ],
70
                    'directives'       => [
71
                        'description' => 'A list of all directives supported by this server.',
72
                        'type'        => GraphQLNonNull(GraphQLList(GraphQLNonNull(__Directive()))),
73
                        'resolve'     => function (SchemaInterface $schema): array {
74
                            return $schema->getDirectives();
75
                        },
76
                    ],
77
                ];
78
            }
79
        ]);
80
    }
81
82
    return $instance;
83
}
84
85
/**
86
 * @return ObjectType
87
 */
88
function __Directive(): ObjectType
89
{
90
    static $instance = null;
91
92
    if (null === $instance) {
93
        $instance = GraphQLObjectType([
94
            'name'            => '__Directive',
95
            'isIntrospection' => true,
96
            'description'     =>
97
                'A Directive provides a way to describe alternate runtime execution and ' .
98
                'type validation behavior in a GraphQL document.' .
99
                "\n\nIn some cases, you need to provide options to alter GraphQL's " .
100
                'execution behavior in ways field arguments will not suffice, such as ' .
101
                'conditionally including or skipping a field. Directives provide this by ' .
102
                'describing additional information to the executor.',
103
            'fields'          => function () {
104
                return [
105
                    'name'        => ['type' => GraphQLNonNull(GraphQLString())],
106
                    'description' => ['type' => GraphQLString()],
107
                    'locations'   => [
108
                        'type' => GraphQLNonNull(GraphQLList(GraphQLNonNull(__DirectiveLocation()))),
109
                    ],
110
                    'args'        => [
111
                        'type'    => GraphQLNonNull(GraphQLList(GraphQLNonNull(__InputValue()))),
112
                        'resolve' => function (DirectiveInterface $directive): array {
113
                            return $directive->getArgs() ?: [];
114
                        },
115
                    ],
116
                ];
117
            }
118
        ]);
119
    }
120
121
    return $instance;
122
}
123
124
/**
125
 * @return EnumType
126
 */
127
function __DirectiveLocation(): EnumType
128
{
129
    static $instance = null;
130
131
    if (null === $instance) {
132
        $instance = GraphQLEnumType([
133
            'name'            => '__DirectiveLocation',
134
            'isIntrospection' => true,
135
            'description'     =>
136
                'A Directive can be adjacent to many parts of the GraphQL language, a ' .
137
                '__DirectiveLocation describes one such possible adjacencies.',
138
            'values'          => [
139
                DirectiveLocationEnum::QUERY                  => [
140
                    'description' => 'Location adjacent to a query operation.',
141
                ],
142
                DirectiveLocationEnum::MUTATION               => [
143
                    'description' => 'Location adjacent to a mutation operation.',
144
                ],
145
                DirectiveLocationEnum::SUBSCRIPTION           => [
146
                    'description' => 'Location adjacent to a subscription operation.',
147
                ],
148
                DirectiveLocationEnum::FIELD                  => [
149
                    'description' => 'Location adjacent to a field.',
150
                ],
151
                DirectiveLocationEnum::FRAGMENT_DEFINITION    => [
152
                    'description' => 'Location adjacent to a fragment definition.',
153
                ],
154
                DirectiveLocationEnum::FRAGMENT_SPREAD        => [
155
                    'description' => 'Location adjacent to a fragment spread.',
156
                ],
157
                DirectiveLocationEnum::INLINE_FRAGMENT        => [
158
                    'description' => 'Location adjacent to an inline fragment.',
159
                ],
160
                DirectiveLocationEnum::SCHEMA                 => [
161
                    'description' => 'Location adjacent to a schema definition.',
162
                ],
163
                DirectiveLocationEnum::SCALAR                 => [
164
                    'description' => 'Location adjacent to a scalar definition.',
165
                ],
166
                DirectiveLocationEnum::OBJECT                 => [
167
                    'description' => 'Location adjacent to an object type definition.',
168
                ],
169
                DirectiveLocationEnum::FIELD_DEFINITION       => [
170
                    'description' => 'Location adjacent to a field definition.',
171
                ],
172
                DirectiveLocationEnum::ARGUMENT_DEFINITION    => [
173
                    'description' => 'Location adjacent to an argument definition.',
174
                ],
175
                DirectiveLocationEnum::INTERFACE              => [
176
                    'description' => 'Location adjacent to an interface definition.',
177
                ],
178
                DirectiveLocationEnum::UNION                  => [
179
                    'description' => 'Location adjacent to a union definition.',
180
                ],
181
                DirectiveLocationEnum::ENUM                   => [
182
                    'description' => 'Location adjacent to an enum definition.',
183
                ],
184
                DirectiveLocationEnum::ENUM_VALUE             => [
185
                    'description' => 'Location adjacent to an enum value definition.',
186
                ],
187
                DirectiveLocationEnum::INPUT_OBJECT           => [
188
                    'description' => 'Location adjacent to an input object type definition.',
189
                ],
190
                DirectiveLocationEnum::INPUT_FIELD_DEFINITION => [
191
                    'description' => 'Location adjacent to an input object field definition.',
192
                ],
193
            ],
194
        ]);
195
    }
196
197
    return $instance;
198
}
199
200
/**
201
 * @return ObjectType
202
 */
203
function __Type(): ObjectType
204
{
205
    static $instance = null;
206
207
    if (null === $instance) {
208
        $instance = GraphQLObjectType([
209
            'name'            => '__Type',
210
            'isIntrospection' => true,
211
            'description'     =>
212
                'The fundamental unit of any GraphQL Schema is the type. There are ' .
213
                'many kinds of types in GraphQL as represented by the `__TypeKind` enum.' .
214
                '\n\nDepending on the kind of a type, certain fields describe ' .
215
                'information about that type. Scalar types provide no information ' .
216
                'beyond a name and description, while Enum types provide their values. ' .
217
                'Object and Interface types provide the fields they describe. Abstract ' .
218
                'types, Union and Interface, provide the Object types possible ' .
219
                'at runtime. List and NonNull types compose other types.',
220
            'fields'          => function () {
221
                return [
222
                    'kind'          => [
223
                        'type'    => GraphQLNonNull(__TypeKind()),
224
                        'resolve' => function (TypeInterface $type) {
225
                            if ($type instanceof ScalarType) {
226
                                return TypeKindEnum::SCALAR;
227
                            }
228
                            if ($type instanceof ObjectType) {
229
                                return TypeKindEnum::OBJECT;
230
                            }
231
                            if ($type instanceof InterfaceType) {
232
                                return TypeKindEnum::INTERFACE;
233
                            }
234
                            if ($type instanceof UnionType) {
235
                                return TypeKindEnum::UNION;
236
                            }
237
                            if ($type instanceof EnumType) {
238
                                return TypeKindEnum::ENUM;
239
                            }
240
                            if ($type instanceof InputObjectType) {
241
                                return TypeKindEnum::INPUT_OBJECT;
242
                            }
243
                            if ($type instanceof ListType) {
244
                                return TypeKindEnum::LIST;
245
                            }
246
                            if ($type instanceof NonNullType) {
247
                                return TypeKindEnum::NON_NULL;
248
                            }
249
250
                            throw new \Exception(sprintf('Unknown kind of type: %s', $type));
251
                        },
252
                    ],
253
                    'name'          => ['type' => GraphQLString()],
254
                    'description'   => ['type' => GraphQLString()],
255
                    'fields'        => [
256
                        'type'    => GraphQLList(GraphQLNonNull(__Field())),
257
                        'args'    => [
258
                            'includeDeprecated' => ['type' => GraphQLBoolean(), 'defaultValue' => false],
259
                        ],
260
                        'resolve' => function (TypeInterface $type, array $args): ?array {
261
                            list ($includeDeprecated) = $args;
262
263
                            if ($type instanceof ObjectType || $type instanceof InterfaceType) {
264
                                $fields = array_values($type->getFields());
265
266
                                if (!$includeDeprecated) {
267
                                    $fields = array_filter($fields, function (Field $field) {
268
                                        return !$field->isDeprecated();
269
                                    });
270
                                }
271
272
                                return $fields;
273
                            }
274
275
                            return null;
276
                        },
277
                    ],
278
                    'interfaces'    => [
279
                        'type'    => GraphQLList(GraphQLNonNull(__Type())),
280
                        'resolve' => function (TypeInterface $type): ?array {
281
                            return $type instanceof ObjectType ? $type->getInterfaces() : null;
282
                        },
283
                    ],
284
                    'possibleTypes' => [
285
                        'type'    => GraphQLList(GraphQLNonNull(__Type())),
286
                        'resolve' => function (TypeInterface $type, $args, $context, $info): ?array {
287
                            /** @var SchemaInterface $schema */
288
                            list ($schema) = $info;
289
                            return $type instanceof AbstractType ? $schema->getPossibleTypes($type) : null;
290
                        },
291
                    ],
292
                    'enumValues'    => [
293
                        'type'    => GraphQLList(GraphQLNonNull(__EnumValue())),
294
                        'args'    => [
295
                            'includeDeprecated' => ['type' => GraphQLBoolean(), 'defaultValue' => false],
296
                        ],
297
                        'resolve' => function (TypeInterface $type, array $args): ?array {
298
                            list ($includeDeprecated) = $args;
299
300
                            if ($type instanceof EnumType) {
301
                                $values = array_values($type->getValues());
302
303
                                if (!$includeDeprecated) {
304
                                    $values = array_filter($values, function (Field $field) {
305
                                        return !$field->isDeprecated();
306
                                    });
307
                                }
308
309
                                return $values;
310
                            }
311
312
                            return null;
313
                        },
314
                    ],
315
                    'inputFields'   => [
316
                        'type'    => GraphQLList(GraphQLNonNull(__InputValue())),
317
                        'resolve' => function (TypeInterface $type): ?array {
318
                            return $type instanceof InputObjectType ? $type->getFields() : null;
319
                        },
320
                    ],
321
                    'ofType'        => ['type' => __Type()],
322
                ];
323
            }
324
        ]);
325
    }
326
327
    return $instance;
328
}
329
330
/**
331
 * @return ObjectType
332
 */
333
function __Field(): ObjectType
334
{
335
    static $instance = null;
336
337
    if (null === $instance) {
338
        $instance = GraphQLObjectType([
339
            'name'            => '__Field',
340
            'isIntrospection' => true,
341
            'description'     =>
342
                'Object and Interface types are described by a list of Fields, each of ' .
343
                'which has a name, potentially a list of arguments, and a return type.',
344
            'fields'          => function () {
345
                return [
346
                    'name'              => ['type' => GraphQLNonNull(GraphQLString())],
347
                    'description'       => ['type' => GraphQLString()],
348
                    'args'              => [
349
                        'type'    => GraphQLNonNull(GraphQLList(GraphQLNonNull(__InputValue()))),
350
                        'resolve' => function (DirectiveInterface $directive): array {
351
                            return $directive->getArgs() ?: [];
352
                        },
353
                    ],
354
                    'type'              => ['type' => GraphQLNonNull(__Type())],
355
                    'isDeprecated'      => ['type' => GraphQLNonNull(GraphQLBoolean())],
356
                    'deprecationReason' => ['type' => GraphQLString()],
357
                ];
358
            }
359
        ]);
360
    }
361
362
    return $instance;
363
}
364
365
/**
366
 * @return ObjectType
367
 */
368
function __InputValue(): ObjectType
369
{
370
    static $instance = null;
371
372
    if (null === $instance) {
373
        $instance = GraphQLObjectType([
374
            'name'            => '__InputValue',
375
            'isIntrospection' => true,
376
            'description'     =>
377
                'Arguments provided to Fields or Directives and the input fields of an ' .
378
                'InputObject are represented as Input Values which describe their type ' .
379
                'and optionally a default value.',
380
            'fields'          => function () {
381
                return [
382
                    'name'         => ['type' => GraphQLNonNull(GraphQLString())],
383
                    'description'  => ['type' => GraphQLString()],
384
                    'type'         => ['type' => GraphQLNonNull(__Type())],
385
                    'defaultValue' => [
386
                        'type'        => GraphQLString(),
387
                        'description' =>
388
                            'A GraphQL-formatted string representing the default value for this ' .
389
                            'input value.',
390
                        '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

390
                        '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...
391
                            // TODO: Implement this when we have support for printing AST.
392
                            return null;
393
                        }
394
                    ],
395
                ];
396
            }
397
        ]);
398
    }
399
400
    return $instance;
401
}
402
403
/**
404
 * @return ObjectType
405
 */
406
function __EnumValue(): ObjectType
407
{
408
    static $instance = null;
409
410
    if (null === $instance) {
411
        $instance = GraphQLObjectType([
412
            'name'            => '__EnumValue',
413
            'isIntrospection' => true,
414
            'description'     =>
415
                'One possible value for a given Enum. Enum values are unique values, not ' .
416
                'a placeholder for a string or numeric value. However an Enum value is ' .
417
                'returned in a JSON response as a string.',
418
            'fields'          => function () {
419
                return [
420
                    'name'              => ['type' => GraphQLNonNull(GraphQLString())],
421
                    'description'       => ['type' => GraphQLString()],
422
                    'isDeprecated'      => ['type' => GraphQLNonNull(GraphQLBoolean())],
423
                    'deprecationReason' => ['type' => GraphQLString()],
424
                ];
425
            }
426
        ]);
427
    }
428
429
    return $instance;
430
}
431
432
function __TypeKind(): EnumType
433
{
434
    static $instance = null;
435
436
    if (null === $instance) {
437
        $instance = GraphQLEnumType([
438
            'name'            => '__TypeKind',
439
            'isIntrospection' => true,
440
            'description'     => 'An enum describing what kind of type a given `__Type` is.',
441
            'values'          => [
442
                TypeKindEnum::SCALAR => [
443
                    'description' => 'Indicates this type is a scalar.',
444
                ],
445
                TypeKindEnum::OBJECT => [
446
                    'description' => 'Indicates this type is an object. `fields` and `interfaces` are valid fields.',
447
                ],
448
                TypeKindEnum::INTERFACE => [
449
                    'description' => 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.',
450
                ],
451
                TypeKindEnum::UNION => [
452
                    'description' => 'Indicates this type is a union. `possibleTypes` is a valid field.',
453
                ],
454
                TypeKindEnum::ENUM => [
455
                    'description' => 'Indicates this type is an enum. `enumValues` is a valid field.',
456
                ],
457
                TypeKindEnum::INPUT_OBJECT => [
458
                    'description' => 'Indicates this type is an input object. `inputFields` is a valid field.',
459
                ],
460
                TypeKindEnum::LIST => [
461
                    'description' => 'Indicates this type is a list. `ofType` is a valid field.',
462
                ],
463
                TypeKindEnum::NON_NULL => [
464
                    'description' => 'Indicates this type is a non-null. `ofType` is a valid field.',
465
                ],
466
            ],
467
        ]);
468
    }
469
470
    return $instance;
471
}
472
473
/**
474
 * @return Field
475
 * @throws \TypeError
476
 */
477
function SchemaMetaFieldDefinition(): Field
478
{
479
    return new Field([
480
        'name' => '__schema',
481
        'type' => GraphQLNonNull(__Schema()),
482
        'description' => 'Access the current type schema of this server.',
483
        'resolve' => function ($source, $args, $context, $info): SchemaInterface {
484
            list ($schema) = $info;
485
            return $schema;
486
        }
487
    ]);
488
}
489
490
/**
491
 * @return Field
492
 * @throws \TypeError
493
 */
494
function TypeMetaFieldDefinition(): Field
495
{
496
    return new Field([
497
        'name' => '__type',
498
        'type' => __Type(),
499
        'description' => 'Request the type information of a single type.',
500
        'args' => [
501
            'name' => ['type' => GraphQLNonNull(GraphQLString())],
502
        ],
503
        'resolve' => function ($source, $args, $context, $info): TypeInterface {
504
            /** @var SchemaInterface $schema */
505
            list ($name) = $args;
506
            list ($schema) = $info;
507
            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\Defin...\Contract\TypeInterface. Consider adding an additional type-check to rule them out.
Loading history...
508
        }
509
    ]);
510
}
511
512
/**
513
 * @return Field
514
 * @throws \TypeError
515
 */
516
function TypeNameMetaFieldDefinition(): Field
517
{
518
    return new Field([
519
        'name' => '__typename',
520
        'type' => GraphQLNonNull(GraphQLString()),
521
        'description' => 'The name of the current Object type at runtime.',
522
        'resolve' => function ($source, $args, $context, $info): string {
523
            /** @var TypeInterface $parentType */
524
            list ($parentType) = $info;
525
            return $parentType->getName();
526
        }
527
    ]);
528
}
529
530
/**
531
 * @return array
532
 */
533
function introspectionTypes(): array
534
{
535
    return [
536
        __Schema(),
537
        __Directive(),
538
        __DirectiveLocation(),
539
        __Type(),
540
        __Field(),
541
        __InputValue(),
542
        __EnumValue(),
543
        __TypeKind(),
544
    ];
545
}
546
547
/**
548
 * @param TypeInterface $type
549
 * @return bool
550
 */
551
function isIntrospectionType(TypeInterface $type): bool
552
{
553
    return arraySome(
554
        introspectionTypes(),
555
        function (TypeInterface $introspectionType) use ($type) {
556
            return $type->getName() === $introspectionType->getName();
557
        }
558
    );
559
}
560