Completed
Push — master ( a5a1ff...7fe571 )
by Christoffer
05:12 queued 01:59
created

DefinitionPrinter::printIntrospectionSchema()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 2
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Schema;
4
5
use Digia\GraphQL\Error\InvariantException;
6
use Digia\GraphQL\Error\PrintException;
7
use Digia\GraphQL\Type\Definition\Argument;
8
use Digia\GraphQL\Type\Definition\DeprecationAwareInterface;
9
use Digia\GraphQL\Type\Definition\DescriptionAwareInterface;
10
use Digia\GraphQL\Type\Definition\Directive;
11
use Digia\GraphQL\Type\Definition\EnumType;
12
use Digia\GraphQL\Type\Definition\EnumValue;
13
use Digia\GraphQL\Type\Definition\Field;
14
use Digia\GraphQL\Type\Definition\InputField;
15
use Digia\GraphQL\Type\Definition\InputObjectType;
16
use Digia\GraphQL\Type\Definition\InputValueInterface;
17
use Digia\GraphQL\Type\Definition\InterfaceType;
18
use Digia\GraphQL\Type\Definition\NamedTypeInterface;
19
use Digia\GraphQL\Type\Definition\ObjectType;
20
use Digia\GraphQL\Type\Definition\ScalarType;
21
use Digia\GraphQL\Type\Definition\TypeInterface;
22
use Digia\GraphQL\Type\Definition\UnionType;
23
use function Digia\GraphQL\printNode;
24
use function Digia\GraphQL\Type\isIntrospectionType;
25
use function Digia\GraphQL\Type\isSpecifiedScalarType;
26
use function Digia\GraphQL\Type\String;
27
use function Digia\GraphQL\Util\arrayEvery;
28
use function Digia\GraphQL\Util\astFromValue;
29
use function Digia\GraphQL\Util\toString;
30
31
class DefinitionPrinter implements DefinitionPrinterInterface
32
{
33
    /**
34
     * @var array
35
     */
36
    protected $options;
37
38
    /**
39
     * @inheritdoc
40
     * @throws PrintException
41
     * @throws InvariantException
42
     */
43
    public function printSchema(SchemaInterface $schema, array $options = []): string
44
    {
45
        $this->options = $options;
46
47
        return $this->printFilteredSchema(
48
            $schema,
49
            function (Directive $directive): bool {
50
                return !isSpecifiedDirective($directive);
51
            },
52
            function (TypeInterface $type): bool {
53
                return !isSpecifiedScalarType($type) && !isIntrospectionType($type);
54
            }
55
        );
56
    }
57
58
    /**
59
     * @inheritdoc
60
     * @throws PrintException
61
     * @throws InvariantException
62
     */
63
    public function printIntrospectionSchema(SchemaInterface $schema, array $options = []): string
64
    {
65
        $this->options = $options;
66
67
        return $this->printFilteredSchema(
68
            $schema,
69
            function (Directive $directive): bool {
70
                return isSpecifiedDirective($directive);
71
            },
72
            function (TypeInterface $type): bool {
73
                return isIntrospectionType($type);
74
            }
75
        );
76
    }
77
78
    /**
79
     * @param mixed $definition
80
     * @return string
0 ignored issues
show
Bug introduced by
The type Digia\GraphQL\Type\String was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
81
     * @throws PrintException
82
     * @throws InvariantException
83
     */
84
    public function print(DefinitionInterface $definition): string
85
    {
86
        if ($definition instanceof Schema) {
87
            return $this->printSchemaDefinition($definition);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->printSchemaDefinition($definition) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
88
        }
89
        if ($definition instanceof NamedTypeInterface) {
90
            return $this->printType($definition);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->printType($definition) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
91
        }
92
93
        throw new PrintException(\sprintf('Invalid definition object: %s.', toString($definition)));
94
    }
95
96
    /**
97
     * @param SchemaInterface $schema
98
     * @param callable        $directiveFilter
99
     * @param callable        $typeFilter
100
     * @return string
101
     * @throws PrintException
102
     * @throws InvariantException
103
     */
104
    protected function printFilteredSchema(
105
        SchemaInterface $schema,
106
        callable $directiveFilter,
107
        callable $typeFilter
108
    ): string {
109
        /** @noinspection PhpParamsInspection */
110
        $lines = \array_filter(\array_merge(
111
            [$this->printOne($schema)],
112
            $this->printMany($this->getSchemaDirectives($schema, $directiveFilter)),
113
            $this->printMany($this->getSchemaTypes($schema, $typeFilter))
114
        ));
115
116
        return printArray("\n\n", $lines) . "\n";
0 ignored issues
show
Bug Best Practice introduced by
The expression return printArray(' ', $lines) . ' ' returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
117
    }
118
119
    /**
120
     * @param SchemaInterface $schema
121
     * @param callable        $filter
122
     * @return array
123
     */
124
    protected function getSchemaDirectives(SchemaInterface $schema, callable $filter): array
125
    {
126
        return \array_filter($schema->getDirectives(), $filter);
127
    }
128
129
    /**
130
     * @param SchemaInterface $schema
131
     * @param callable        $filter
132
     * @return array
133
     */
134
    protected function getSchemaTypes(SchemaInterface $schema, callable $filter): array
135
    {
136
        $types = \array_filter(\array_values($schema->getTypeMap()), $filter);
137
138
        \usort($types, function (NamedTypeInterface $typeA, NamedTypeInterface $typeB) {
139
            return \strcasecmp($typeA->getName(), $typeB->getName());
140
        });
141
142
        return $types;
143
    }
144
145
    /**
146
     * @param Schema $definition
147
     * @return string
148
     */
149
    protected function printSchemaDefinition(Schema $definition): string
150
    {
151
        if ($this->isSchemaOfCommonNames($definition)) {
152
            return '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '' returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
153
        }
154
155
        $operationTypes = [];
156
157
        if (null !== ($queryType = $definition->getQueryType())) {
158
            $operationTypes[] = "  query: {$queryType->getName()}";
159
        }
160
161
        if (null !== ($mutationType = $definition->getMutationType())) {
162
            $operationTypes[] = "  mutation: {$mutationType->getName()}";
163
        }
164
165
        if (null !== ($subscriptionType = $definition->getSubscriptionType())) {
166
            $operationTypes[] = "  subscription: {$subscriptionType->getName()}";
167
        }
168
169
        return printLines([
0 ignored issues
show
Bug Best Practice introduced by
The expression return printLines(array(...$operationTypes), '}')) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
170
            'schema {\n',
171
            printLines($operationTypes),
172
            '}'
173
        ]);
174
    }
175
176
    /**
177
     * GraphQL schema define root types for each type of operation. These types are
178
     * the same as any other type and can be named in any manner, however there is
179
     * a common naming convention:
180
     *
181
     *   schema {
182
     *     query: Query
183
     *     mutation: Mutation
184
     *     subscription: Subscription
185
     *   }
186
     *
187
     * When using this naming convention, the schema description can be omitted.
188
     *
189
     * @param SchemaInterface $schema
190
     * @return bool
191
     */
192
    protected function isSchemaOfCommonNames(SchemaInterface $schema): bool
193
    {
194
        if (null !== ($queryType = $schema->getQueryType()) &&
195
            $queryType->getName() !== 'Query') {
196
            return false;
197
        }
198
199
        if (null !== ($mutationType = $schema->getMutationType()) &&
200
            $mutationType->getName() !== 'Mutation') {
201
            return false;
202
        }
203
204
        if (null !== ($subscriptionType = $schema->getSubscriptionType()) &&
205
            $subscriptionType->getName() !== 'Subscription') {
206
            return false;
207
        }
208
209
        return true;
210
    }
211
212
    /**
213
     * @param NamedTypeInterface $type
214
     * @return string
215
     * @throws PrintException
216
     * @throws InvariantException
217
     */
218
    protected function printType(NamedTypeInterface $type): string
219
    {
220
        if ($type instanceof ScalarType) {
221
            return $this->printScalarType($type);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->printScalarType($type) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
222
        }
223
        if ($type instanceof ObjectType) {
224
            return $this->printObjectType($type);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->printObjectType($type) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
225
        }
226
        if ($type instanceof InterfaceType) {
227
            return $this->printInterfaceType($type);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->printInterfaceType($type) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
228
        }
229
        if ($type instanceof UnionType) {
230
            return $this->printUnionType($type);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->printUnionType($type) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
231
        }
232
        if ($type instanceof EnumType) {
233
            return $this->printEnumType($type);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->printEnumType($type) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
234
        }
235
        if ($type instanceof InputObjectType) {
236
            return $this->printInputObjectType($type);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->printInputObjectType($type) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
237
        }
238
239
        throw new PrintException(\sprintf('Unknown type: %s', (string)$type));
240
    }
241
242
    /**
243
     * @param ScalarType $type
244
     * @return string
245
     */
246
    protected function printScalarType(ScalarType $type): string
247
    {
248
        return printLines([
0 ignored issues
show
Bug Best Practice introduced by
The expression return printLines(array(...ar '.$type->getName())) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
249
            $this->printDescription($type),
250
            "scalar {$type->getName()}"
251
        ]);
252
    }
253
254
    /**
255
     * @param ObjectType $type
256
     * @return string
257
     * @throws InvariantException
258
     */
259
    protected function printObjectType(ObjectType $type): string
260
    {
261
        $description = $this->printDescription($type);
262
        $name        = $type->getName();
263
        $implements  = $type->hasInterfaces()
264
            ? ' implements ' . printArray(' & ', \array_map(function (InterfaceType $interface) {
265
                return $interface->getName();
266
            }, $type->getInterfaces()))
267
            : '';
268
        $fields      = $this->printFields($type->getFields());
269
270
        return printLines([
0 ignored issues
show
Bug Best Practice introduced by
The expression return printLines(array(...ts.' {', $fields, '}')) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
271
            $description,
272
            "type {$name}{$implements} {",
273
            $fields,
274
            '}'
275
        ]);
276
    }
277
278
    /**
279
     * @param InterfaceType $type
280
     * @return string
281
     * @throws InvariantException
282
     */
283
    protected function printInterfaceType(InterfaceType $type): string
284
    {
285
        $description = $this->printDescription($type);
286
        $fields      = $this->printMany($type->getFields());
287
288
        return printLines([
0 ignored issues
show
Bug Best Practice introduced by
The expression return printLines(array(...().' {', $fields, '}')) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
289
            $description,
290
            "interface {$type->getName()} {",
291
            $fields,
292
            '}'
293
        ]);
294
    }
295
296
    /**
297
     * @param UnionType $type
298
     * @return string
299
     * @throws InvariantException
300
     */
301
    protected function printUnionType(UnionType $type): string
302
    {
303
        $description = $this->printDescription($type);
304
        $types       = printArray(' | ', $type->getTypes());
305
306
        return printLines([
0 ignored issues
show
Bug Best Practice introduced by
The expression return printLines(array(...etName().' = '.$types)) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
307
            $description,
308
            "union {$type->getName()} = {$types}"
309
        ]);
310
    }
311
312
    /**
313
     * @param EnumType $type
314
     * @return string
315
     * @throws InvariantException
316
     */
317
    protected function printEnumType(EnumType $type): string
318
    {
319
        $description = $this->printDescription($type);
320
        $values      = $this->printEnumValues($type->getValues());
321
322
        return printLines([
0 ignored issues
show
Bug Best Practice introduced by
The expression return printLines(array(...().' {', $values, '}')) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
323
            $description,
324
            "enum {$type->getName()} {",
325
            $values,
326
            '}'
327
        ]);
328
    }
329
330
    protected function printEnumValues(array $values): string
331
    {
332
        return printLines(\array_map(function (EnumValue $value, int $i): string {
333
            $description = $this->printDescription($value, '  ', 0 === $i);
334
            $name        = $value->getName();
335
            $deprecated  = $this->printDeprecated($value);
336
            return printLines([
337
                $description,
338
                "  {$name} {$deprecated}"
339
            ]);
340
        }, $values));
341
    }
342
343
    /**
344
     * @param InputObjectType $type
345
     * @return string
346
     * @throws InvariantException
347
     */
348
    protected function printInputObjectType(InputObjectType $type): string
349
    {
350
        $description = $this->printDescription($type);
351
        $fields      = \array_map(function (InputField $field, int $i): string {
352
            $description = $this->printDescription($field, '  ', 0 === $i);
353
            $inputValue  = $this->printInputValue($field);
354
            return printLines([
355
                $description,
356
                "  {$inputValue}"
357
            ]);
358
        }, \array_values($type->getFields()));
359
360
        return printLines([
0 ignored issues
show
Bug Best Practice introduced by
The expression return printLines(array(...().' {', $fields, '}')) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
361
            $description,
362
            "input {$type->getName()} {",
363
            $fields,
364
            '}'
365
        ]);
366
    }
367
368
    /**
369
     * @param InputValueInterface $inputValue
370
     * @return string
371
     */
372
    protected function printInputValue(InputValueInterface $inputValue): string
373
    {
374
        $type         = $inputValue->getType();
375
        $name         = $inputValue->getName();
376
        $defaultValue = printNode(astFromValue($inputValue->getDefaultValue(), $type));
377
378
        return $inputValue->hasDefaultValue()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $inputValue->hasD...alue : $name.': '.$type returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
379
            ? "{$name}: {$type} = {$defaultValue}"
380
            : "{$name}: {$type}";
381
    }
382
383
    /**
384
     * @param array $fields
385
     * @return string
386
     */
387
    protected function printFields(array $fields): string
388
    {
389
        return printLines(\array_map(function (Field $field): string {
0 ignored issues
show
Bug Best Practice introduced by
The expression return printLines(array_...array_values($fields))) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
390
            $description = $this->printDescription($field);
391
            $name        = $field->getName();
392
            $arguments   = $this->printArguments($field->getArguments());
393
            $type        = (string)$field->getType();
394
            $deprecated  = $this->printDeprecated($field);
395
            return printLines([
396
                $description,
397
                "  {$name}{$arguments}: {$type}{$deprecated}"
398
            ]);
399
        }, \array_values($fields)));
400
    }
401
402
    protected function printArguments(array $arguments, string $indentation = ''): string
403
    {
404
        if (empty($arguments)) {
405
            return '';
406
        }
407
408
        // If every arg does not have a description, print them on one line.
409
        if (arrayEvery($arguments, function (Argument $argument): bool {
410
            return !$argument->hasDescription();
411
        })) {
412
            return printInputFields(\array_map(function (Argument $argument) {
413
                return $this->printInputValue($argument);
414
            }, $arguments));
415
        }
416
417
        $args = \array_map(function (Argument $argument) use ($indentation) {
418
            $description = $this->printDescription($argument);
419
            $inputValue  = $this->printInputValue($argument);
420
            return printLines([
421
                $description,
422
                "  {$indentation}{$inputValue}"
423
            ]);
424
        }, $arguments);
425
426
        return printLines([
427
            '(',
428
            $args,
429
            $indentation . ')'
430
        ]);
431
    }
432
433
    /**
434
     * @param DeprecationAwareInterface $fieldOrEnumValue
435
     * @return string
436
     */
437
    protected function printDeprecated(DeprecationAwareInterface $fieldOrEnumValue): string
438
    {
439
        if (!$fieldOrEnumValue->isDeprecated()) {
440
            return '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '' returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
441
        }
442
443
        $reason = $fieldOrEnumValue->getDeprecationReason();
444
445
        if (null === $reason || '' === $reason || DEFAULT_DEPRECATION_REASON === $reason) {
446
            return '@deprecated';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '@deprecated' returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
447
        }
448
449
        $reasonValue = printNode(astFromValue($reason, String()));
450
451
        return "@deprecated(reason: {$reasonValue})";
0 ignored issues
show
Bug Best Practice introduced by
The expression return '@deprecated(reason: '.$reasonValue.')' returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
452
    }
453
454
    /**
455
     * @param mixed  $type
456
     * @param string $indentation
457
     * @param bool   $isFirstInBlock
458
     * @return string
459
     */
460
    protected function printDescription(
461
        $type,
462
        string $indentation = '',
463
        bool $isFirstInBlock = true
464
    ): string {
465
        if (!($type instanceof DescriptionAwareInterface)) {
466
            return '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return '' returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
467
        }
468
469
        $lines      = descriptionLines($type->getDescription(), 120 - \strlen($indentation));
470
        $linesCount = \count($lines);
471
472
        if (isset($this->options['commentDescriptions']) && true === $this->options['commentDescriptions']) {
473
            return $this->printDescriptionWithComments($lines, $indentation, $isFirstInBlock);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->printDescr...ation, $isFirstInBlock) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
474
        }
475
476
        $description = $indentation && !$isFirstInBlock
477
            ? "\n" . $indentation . '"""'
478
            : $indentation . '"""';
479
480
        // In some circumstances, a single line can be used for the description.
481
        if (
482
            $linesCount === 1 &&
483
            ($firstLineLength = \strlen($lines[0])) < 70 &&
484
            $lines[0][$firstLineLength - 1] !== '"'
485
        ) {
486
            return $description . escapeQuote($lines[0]) . '"""' . "\n";
0 ignored issues
show
Bug Best Practice introduced by
The expression return $description . es...lines[0]) . '"""' . ' ' returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
487
        }
488
489
        // Format a multi-line block quote to account for leading space.
490
        $hasLeadingSpace = $lines[0][0] === ' ' || $lines[0][0] === "\t";
491
        if (!$hasLeadingSpace) {
492
            $description .= "\n";
493
        }
494
495
        for ($i = 0; $i < $linesCount; $i++) {
496
            $description .= $i !== 0 || !$hasLeadingSpace
497
                ? $description .= $indentation
498
                : escapeQuote($lines[$i]) . "\n";
499
        }
500
501
        $description .= $indentation . '"""';
502
503
        return $description;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $description returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
504
    }
505
506
    protected function printDescriptionWithComments(array $lines, string $indentation, bool $isFirstInBlock): string
507
    {
508
        $description = $indentation && !$isFirstInBlock ? "\n" : '';
509
        $linesCount  = \count($lines);
510
511
        for ($i = 0; $i < $linesCount; $i++) {
512
            $description .= $lines[$i] === ''
513
                ? $indentation . '#' . "\n"
514
                : $indentation . '# ' . $lines[$i] . "\n";
515
        }
516
517
        return $description;
518
    }
519
520
    /**
521
     * @param DefinitionInterface $definition
522
     * @return string
523
     * @throws PrintException
524
     * @throws InvariantException
525
     */
526
    protected function printOne(DefinitionInterface $definition): string
527
    {
528
        return $this->print($definition);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->print($definition) returns the type string which is incompatible with the documented return type Digia\GraphQL\Type\String.
Loading history...
529
    }
530
531
    /**
532
     * @param DefinitionInterface[] $definitions
533
     * @return array
534
     */
535
    protected function printMany(array $definitions): array
536
    {
537
        return \array_map(function ($definition) {
538
            return $this->print($definition);
539
        }, $definitions);
540
    }
541
}
542