SchemaPrinter::printDeprecated()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 10
c 0
b 0
f 0
cc 4
nc 3
nop 1
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Utils;
6
7
use GraphQL\Error\Error;
8
use GraphQL\Language\Printer;
9
use GraphQL\Type\Definition\Directive;
10
use GraphQL\Type\Definition\EnumType;
11
use GraphQL\Type\Definition\InputObjectType;
12
use GraphQL\Type\Definition\InterfaceType;
13
use GraphQL\Type\Definition\ObjectType;
14
use GraphQL\Type\Definition\ScalarType;
15
use GraphQL\Type\Definition\Type;
16
use GraphQL\Type\Definition\UnionType;
17
use GraphQL\Type\Introspection;
18
use GraphQL\Type\Schema;
19
use function array_filter;
20
use function array_keys;
21
use function array_map;
22
use function array_merge;
23
use function array_values;
24
use function count;
25
use function explode;
26
use function implode;
27
use function ksort;
28
use function mb_strlen;
29
use function preg_match_all;
30
use function sprintf;
31
use function str_replace;
32
use function strlen;
33
use function substr;
34
35
/**
36
 * Given an instance of Schema, prints it in GraphQL type language.
37
 */
38
class SchemaPrinter
39
{
40
    /**
41
     * Accepts options as a second argument:
42
     *
43
     *    - commentDescriptions:
44
     *        Provide true to use preceding comments as the description.
45
     *
46
     * @param bool[] $options
47
     *
48
     * @api
49
     */
50 107
    public static function doPrint(Schema $schema, array $options = []) : string
51
    {
52 107
        return self::printFilteredSchema(
53 107
            $schema,
54
            static function ($type) {
55 107
                return ! Directive::isSpecifiedDirective($type);
56 107
            },
57
            static function ($type) {
58 107
                return ! Type::isBuiltInType($type);
59 107
            },
60 107
            $options
61
        );
62
    }
63
64
    /**
65
     * @param bool[] $options
66
     */
67 109
    private static function printFilteredSchema(Schema $schema, $directiveFilter, $typeFilter, $options) : string
68
    {
69 109
        $directives = array_filter(
70 109
            $schema->getDirectives(),
71
            static function ($directive) use ($directiveFilter) {
72 109
                return $directiveFilter($directive);
73 109
            }
74
        );
75
76 109
        $types = $schema->getTypeMap();
77 109
        ksort($types);
78 109
        $types = array_filter($types, $typeFilter);
79
80 109
        return sprintf(
81 109
            "%s\n",
82 109
            implode(
83 109
                "\n\n",
84 109
                array_filter(
85 109
                    array_merge(
86 109
                        [self::printSchemaDefinition($schema)],
87 109
                        array_map(
88
                            static function ($directive) use ($options) {
89 62
                                return self::printDirective($directive, $options);
90 109
                            },
91 109
                            $directives
92
                        ),
93 109
                        array_map(
94
                            static function ($type) use ($options) {
95 109
                                return self::printType($type, $options);
96 109
                            },
97 109
                            $types
98
                        )
99
                    )
100
                )
101
            )
102
        );
103
    }
104
105 109
    private static function printSchemaDefinition(Schema $schema)
106
    {
107 109
        if (self::isSchemaOfCommonNames($schema)) {
108 105
            return;
109
        }
110
111 4
        $operationTypes = [];
112
113 4
        $queryType = $schema->getQueryType();
114 4
        if ($queryType) {
0 ignored issues
show
introduced by
$queryType is of type GraphQL\Type\Definition\ObjectType, thus it always evaluated to true.
Loading history...
115 4
            $operationTypes[] = sprintf('  query: %s', $queryType->name);
116
        }
117
118 4
        $mutationType = $schema->getMutationType();
119 4
        if ($mutationType) {
0 ignored issues
show
introduced by
$mutationType is of type GraphQL\Type\Definition\ObjectType, thus it always evaluated to true.
Loading history...
120 1
            $operationTypes[] = sprintf('  mutation: %s', $mutationType->name);
121
        }
122
123 4
        $subscriptionType = $schema->getSubscriptionType();
124 4
        if ($subscriptionType) {
0 ignored issues
show
introduced by
$subscriptionType is of type GraphQL\Type\Definition\ObjectType, thus it always evaluated to true.
Loading history...
125 1
            $operationTypes[] = sprintf('  subscription: %s', $subscriptionType->name);
126
        }
127
128 4
        return sprintf("schema {\n%s\n}", implode("\n", $operationTypes));
129
    }
130
131
    /**
132
     * GraphQL schema define root types for each type of operation. These types are
133
     * the same as any other type and can be named in any manner, however there is
134
     * a common naming convention:
135
     *
136
     *   schema {
137
     *     query: Query
138
     *     mutation: Mutation
139
     *   }
140
     *
141
     * When using this naming convention, the schema description can be omitted.
142
     */
143 109
    private static function isSchemaOfCommonNames(Schema $schema)
144
    {
145 109
        $queryType = $schema->getQueryType();
146 109
        if ($queryType && $queryType->name !== 'Query') {
147 4
            return false;
148
        }
149
150 105
        $mutationType = $schema->getMutationType();
151 105
        if ($mutationType && $mutationType->name !== 'Mutation') {
152
            return false;
153
        }
154
155 105
        $subscriptionType = $schema->getSubscriptionType();
156
157 105
        return ! $subscriptionType || $subscriptionType->name === 'Subscription';
158
    }
159
160 62
    private static function printDirective($directive, $options) : string
161
    {
162 62
        return self::printDescription($options, $directive) .
163 62
            'directive @' . $directive->name . self::printArgs($options, $directive->args) .
164 62
            ' on ' . implode(' | ', $directive->locations);
165
    }
166
167 109
    private static function printDescription($options, $def, $indentation = '', $firstInBlock = true) : string
168
    {
169 109
        if (! $def->description) {
170 109
            return '';
171
        }
172 7
        $lines = self::descriptionLines($def->description, 120 - strlen($indentation));
173 7
        if (isset($options['commentDescriptions'])) {
174 2
            return self::printDescriptionWithComments($lines, $indentation, $firstInBlock);
175
        }
176
177 5
        $description = $indentation && ! $firstInBlock
178 2
            ? "\n" . $indentation . '"""'
179 5
            : $indentation . '"""';
180
181
        // In some circumstances, a single line can be used for the description.
182 5
        if (count($lines) === 1 &&
183 5
            mb_strlen($lines[0]) < 70 &&
184 5
            substr($lines[0], -1) !== '"'
185
        ) {
186 3
            return $description . self::escapeQuote($lines[0]) . "\"\"\"\n";
187
        }
188
189
        // Format a multi-line block quote to account for leading space.
190 3
        $hasLeadingSpace = isset($lines[0]) &&
191
            (
192 3
                substr($lines[0], 0, 1) === ' ' ||
193 3
                substr($lines[0], 0, 1) === '\t'
194
            );
195 3
        if (! $hasLeadingSpace) {
196 2
            $description .= "\n";
197
        }
198
199 3
        $lineLength = count($lines);
200 3
        for ($i = 0; $i < $lineLength; $i++) {
201 3
            if ($i !== 0 || ! $hasLeadingSpace) {
202 2
                $description .= $indentation;
203
            }
204 3
            $description .= self::escapeQuote($lines[$i]) . "\n";
205
        }
206 3
        $description .= $indentation . "\"\"\"\n";
207
208 3
        return $description;
209
    }
210
211
    /**
212
     * @return string[]
213
     */
214 7
    private static function descriptionLines(string $description, int $maxLen) : array
215
    {
216 7
        $lines    = [];
217 7
        $rawLines = explode("\n", $description);
218 7
        foreach ($rawLines as $line) {
219 7
            if ($line === '') {
220 2
                $lines[] = $line;
221
            } else {
222
                // For > 120 character long lines, cut at space boundaries into sublines
223
                // of ~80 chars.
224 7
                $sublines = self::breakLine($line, $maxLen);
225 7
                foreach ($sublines as $subline) {
226 7
                    $lines[] = $subline;
227
                }
228
            }
229
        }
230
231 7
        return $lines;
232
    }
233
234
    /**
235
     * @return string[]
236
     */
237 7
    private static function breakLine(string $line, int $maxLen) : array
238
    {
239 7
        if (strlen($line) < $maxLen + 5) {
240 7
            return [$line];
241
        }
242 2
        preg_match_all('/((?: |^).{15,' . ($maxLen - 40) . '}(?= |$))/', $line, $parts);
243 2
        $parts = $parts[0];
244
245 2
        return array_map('trim', $parts);
246
    }
247
248 2
    private static function printDescriptionWithComments($lines, $indentation, $firstInBlock) : string
249
    {
250 2
        $description = $indentation && ! $firstInBlock ? "\n" : '';
251 2
        foreach ($lines as $line) {
252 2
            if ($line === '') {
253 1
                $description .= $indentation . "#\n";
254
            } else {
255 2
                $description .= $indentation . '# ' . $line . "\n";
256
            }
257
        }
258
259 2
        return $description;
260
    }
261
262 5
    private static function escapeQuote($line) : string
263
    {
264 5
        return str_replace('"""', '\\"""', $line);
265
    }
266
267 109
    private static function printArgs($options, $args, $indentation = '') : string
268
    {
269 109
        if (! $args) {
270 93
            return '';
271
        }
272
273
        // If every arg does not have a description, print them on one line.
274 79
        if (Utils::every(
275 79
            $args,
276
            static function ($arg) {
277 79
                return empty($arg->description);
278 79
            }
279
        )) {
280 77
            return '(' . implode(', ', array_map('self::printInputValue', $args)) . ')';
281
        }
282
283 4
        return sprintf(
284 4
            "(\n%s\n%s)",
285 4
            implode(
286 4
                "\n",
287 4
                array_map(
288
                    static function ($arg, $i) use ($indentation, $options) {
289 4
                        return self::printDescription($options, $arg, '  ' . $indentation, ! $i) . '  ' . $indentation .
290 4
                            self::printInputValue($arg);
291 4
                    },
292 4
                    $args,
293 4
                    array_keys($args)
294
                )
295
            ),
296 4
            $indentation
297
        );
298
    }
299
300 79
    private static function printInputValue($arg) : string
301
    {
302 79
        $argDecl = $arg->name . ': ' . (string) $arg->getType();
303 79
        if ($arg->defaultValueExists()) {
304 10
            $argDecl .= ' = ' . Printer::doPrint(AST::astFromValue($arg->defaultValue, $arg->getType()));
305
        }
306
307 79
        return $argDecl;
308
    }
309
310
    /**
311
     * @param bool[] $options
312
     */
313 109
    public static function printType(Type $type, array $options = []) : string
314
    {
315 109
        if ($type instanceof ScalarType) {
316 59
            return self::printScalar($type, $options);
317
        }
318
319 109
        if ($type instanceof ObjectType) {
320 109
            return self::printObject($type, $options);
321
        }
322
323 75
        if ($type instanceof InterfaceType) {
324 60
            return self::printInterface($type, $options);
325
        }
326
327 71
        if ($type instanceof UnionType) {
328 60
            return self::printUnion($type, $options);
329
        }
330
331 67
        if ($type instanceof EnumType) {
332 65
            return self::printEnum($type, $options);
333
        }
334
335 58
        if ($type instanceof InputObjectType) {
336 58
            return self::printInputObject($type, $options);
337
        }
338
339
        throw new Error(sprintf('Unknown type: %s.', Utils::printSafe($type)));
340
    }
341
342
    /**
343
     * @param bool[] $options
344
     */
345 59
    private static function printScalar(ScalarType $type, array $options) : string
346
    {
347 59
        return sprintf('%sscalar %s', self::printDescription($options, $type), $type->name);
348
    }
349
350
    /**
351
     * @param bool[] $options
352
     */
353 109
    private static function printObject(ObjectType $type, array $options) : string
354
    {
355 109
        $interfaces            = $type->getInterfaces();
356 109
        $implementedInterfaces = ! empty($interfaces)
357 60
            ? ' implements ' . implode(
358 60
                ' & ',
359 60
                array_map(
360
                    static function ($i) {
361 60
                        return $i->name;
362 60
                    },
363 60
                    $interfaces
364
                )
365
            )
366 109
            : '';
367
368 109
        return self::printDescription($options, $type) .
369 109
            sprintf("type %s%s {\n%s\n}", $type->name, $implementedInterfaces, self::printFields($options, $type));
370
    }
371
372
    /**
373
     * @param bool[] $options
374
     */
375 109
    private static function printFields($options, $type) : string
376
    {
377 109
        $fields = array_values($type->getFields());
378
379 109
        return implode(
380 109
            "\n",
381 109
            array_map(
382
                static function ($f, $i) use ($options) {
383 109
                    return self::printDescription($options, $f, '  ', ! $i) . '  ' .
384 109
                        $f->name . self::printArgs($options, $f->args, '  ') . ': ' .
385 109
                        (string) $f->getType() . self::printDeprecated($f);
386 109
                },
387 109
                $fields,
388 109
                array_keys($fields)
389
            )
390
        );
391
    }
392
393 109
    private static function printDeprecated($fieldOrEnumVal) : string
394
    {
395 109
        $reason = $fieldOrEnumVal->deprecationReason;
396 109
        if (empty($reason)) {
397 109
            return '';
398
        }
399 1
        if ($reason === '' || $reason === Directive::DEFAULT_DEPRECATION_REASON) {
400 1
            return ' @deprecated';
401
        }
402
403
        return ' @deprecated(reason: ' .
404 1
            Printer::doPrint(AST::astFromValue($reason, Type::string())) . ')';
405
    }
406
407
    /**
408
     * @param bool[] $options
409
     */
410 60
    private static function printInterface(InterfaceType $type, array $options) : string
411
    {
412 60
        return self::printDescription($options, $type) .
413 60
            sprintf("interface %s {\n%s\n}", $type->name, self::printFields($options, $type));
414
    }
415
416
    /**
417
     * @param bool[] $options
418
     */
419 60
    private static function printUnion(UnionType $type, array $options) : string
420
    {
421 60
        return self::printDescription($options, $type) .
422 60
            sprintf('union %s = %s', $type->name, implode(' | ', $type->getTypes()));
423
    }
424
425
    /**
426
     * @param bool[] $options
427
     */
428 65
    private static function printEnum(EnumType $type, array $options) : string
429
    {
430 65
        return self::printDescription($options, $type) .
431 65
            sprintf("enum %s {\n%s\n}", $type->name, self::printEnumValues($type->getValues(), $options));
432
    }
433
434
    /**
435
     * @param bool[] $options
436
     */
437 65
    private static function printEnumValues($values, $options) : string
438
    {
439 65
        return implode(
440 65
            "\n",
441 65
            array_map(
442
                static function ($value, $i) use ($options) {
443 65
                    return self::printDescription($options, $value, '  ', ! $i) . '  ' .
444 65
                        $value->name . self::printDeprecated($value);
445 65
                },
446 65
                $values,
447 65
                array_keys($values)
448
            )
449
        );
450
    }
451
452
    /**
453
     * @param bool[] $options
454
     */
455 58
    private static function printInputObject(InputObjectType $type, array $options) : string
456
    {
457 58
        $fields = array_values($type->getFields());
458
459 58
        return self::printDescription($options, $type) .
460 58
            sprintf(
461 58
                "input %s {\n%s\n}",
462 58
                $type->name,
463 58
                implode(
464 58
                    "\n",
465 58
                    array_map(
466
                        static function ($f, $i) use ($options) {
467 58
                            return self::printDescription($options, $f, '  ', ! $i) . '  ' . self::printInputValue($f);
468 58
                        },
469 58
                        $fields,
470 58
                        array_keys($fields)
471
                    )
472
                )
473
            );
474
    }
475
476
    /**
477
     * @param bool[] $options
478
     *
479
     * @api
480
     */
481 2
    public static function printIntrospectionSchema(Schema $schema, array $options = []) : string
482
    {
483 2
        return self::printFilteredSchema(
484 2
            $schema,
485 2
            [Directive::class, 'isSpecifiedDirective'],
486 2
            [Introspection::class, 'isIntrospectionType'],
487 2
            $options
488
        );
489
    }
490
}
491