Failed Conditions
Pull Request — master (#294)
by Daniel
05:35
created

SchemaPrinter::printUnion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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