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

DefinitionPrinter::printMany()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Digia\GraphQL\Schema;
4
5
use Digia\GraphQL\Error\PrintException;
6
use Digia\GraphQL\Type\Definition\DefinitionInterface;
7
use Digia\GraphQL\Type\Definition\DescriptionAwareInterface;
8
use Digia\GraphQL\Type\Definition\Directive;
9
use Digia\GraphQL\Type\Definition\NamedTypeInterface;
10
use Digia\GraphQL\Type\Definition\ScalarType;
11
use Digia\GraphQL\Type\Definition\TypeInterface;
12
use function Digia\GraphQL\Type\isIntrospectionType;
13
use function Digia\GraphQL\Type\isSpecifiedScalarType;
14
use function Digia\GraphQL\Util\toString;
15
16
class DefinitionPrinter implements DefinitionPrinterInterface
17
{
18
    /**
19
     * @inheritdoc
20
     * @throws PrintException
21
     */
22
    public function printSchema(SchemaInterface $schema, array $options = []): string
23
    {
24
        return $this->printFilteredSchema(
25
            $schema,
26
            function (Directive $directive): bool {
27
                return !isSpecifiedDirective($directive);
28
            },
29
            function (TypeInterface $type): bool {
30
                return !isSpecifiedScalarType($type) && !isIntrospectionType($type);
31
            },
32
            $options
33
        );
34
    }
35
36
    /**
37
     * @inheritdoc
38
     * @throws PrintException
39
     */
40
    public function printIntrospectionSchema(SchemaInterface $schema, array $options = []): string
41
    {
42
        return $this->printFilteredSchema(
43
            $schema,
44
            function (Directive $directive): bool {
45
                return isSpecifiedDirective($directive);
46
            },
47
            function (TypeInterface $type): bool {
48
                return isIntrospectionType($type);
49
            },
50
            $options
51
        );
52
    }
53
54
    /**
55
     * @param mixed $definition
56
     * @param array $options
57
     * @return string
58
     * @throws PrintException
59
     */
60
    public function print(DefinitionInterface $definition, array $options): string
61
    {
62
        if ($definition instanceof Schema) {
63
            return $this->printSchemaDefinition($definition);
64
        }
65
66
        throw new PrintException(\sprintf('Invalid definition object: %s.', toString($definition)));
67
    }
68
69
    /**
70
     * @param SchemaInterface $schema
71
     * @param callable        $directiveFilter
72
     * @param callable        $typeFilter
73
     * @param array           $options
74
     * @return string
75
     * @throws PrintException
76
     */
77
    protected function printFilteredSchema(
78
        SchemaInterface $schema,
79
        callable $directiveFilter,
80
        callable $typeFilter,
81
        array $options
82
    ): string {
83
        /** @noinspection PhpParamsInspection */
84
        return \implode("\n\n", \array_filter(
85
                \array_merge(
86
                    [$this->printOne($schema, $options)],
87
                    $this->printMany($this->getSchemaDirectives($schema, $directiveFilter), $options),
88
                    $this->printMany($this->getSchemaTypes($schema, $typeFilter), $options)
89
                )
90
            )) . "\n";
91
    }
92
93
    /**
94
     * @param SchemaInterface $schema
95
     * @param callable        $filter
96
     * @return array
97
     */
98
    protected function getSchemaDirectives(SchemaInterface $schema, callable $filter): array
99
    {
100
        return \array_filter($schema->getDirectives(), $filter);
101
    }
102
103
    /**
104
     * @param SchemaInterface $schema
105
     * @param callable        $filter
106
     * @return array
107
     */
108
    protected function getSchemaTypes(SchemaInterface $schema, callable $filter): array
109
    {
110
        $types = \array_filter(\array_values($schema->getTypeMap()), $filter);
111
112
        usort($types, function (TypeInterface $typeA, TypeInterface $typeB) {
113
            return \strcasecmp($typeA->getName(), $typeB->getName());
114
        });
115
116
        return $types;
117
    }
118
119
    /**
120
     * @param Schema $definition
121
     * @return string
122
     */
123
    protected function printSchemaDefinition(Schema $definition): string
124
    {
125
        if ($this->isSchemaOfCommonNames($definition)) {
126
            return '';
127
        }
128
129
        $operationTypes = [];
130
131
        if (null !== ($queryType = $definition->getQueryType())) {
132
            $operationTypes[] = "  query: {$queryType->getName()}";
133
        }
134
135
        if (null !== ($mutationType = $definition->getMutationType())) {
136
            $operationTypes[] = "  mutation: {$mutationType->getName()}";
137
        }
138
139
        if (null !== ($subscriptionType = $definition->getSubscriptionType())) {
140
            $operationTypes[] = "  subscription: {$subscriptionType->getName()}";
141
        }
142
143
        return \implode('', [
144
            "schema {\n",
145
            \implode("\n", $operationTypes),
146
            "\n}"
147
        ]);
148
    }
149
150
    /**
151
     * GraphQL schema define root types for each type of operation. These types are
152
     * the same as any other type and can be named in any manner, however there is
153
     * a common naming convention:
154
     *
155
     *   schema {
156
     *     query: Query
157
     *     mutation: Mutation
158
     *     subscription: Subscription
159
     *   }
160
     *
161
     * When using this naming convention, the schema description can be omitted.
162
     *
163
     * @param SchemaInterface $schema
164
     * @return bool
165
     */
166
    protected function isSchemaOfCommonNames(SchemaInterface $schema): bool
167
    {
168
        if (null !== ($queryType = $schema->getQueryType()) &&
169
            $queryType->getName() !== 'Query') {
170
            return false;
171
        }
172
173
        if (null !== ($mutationType = $schema->getMutationType()) &&
174
            $mutationType->getName() !== 'Mutation') {
175
            return false;
176
        }
177
178
        if (null !== ($subscriptionType = $schema->getSubscriptionType()) &&
179
            $subscriptionType->getName() !== 'Subscription') {
180
            return false;
181
        }
182
183
        return true;
184
    }
185
186
    protected function printScalar(ScalarType $type, array $options): string
0 ignored issues
show
Unused Code introduced by
The parameter $type 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

186
    protected function printScalar(/** @scrutinizer ignore-unused */ ScalarType $type, array $options): string

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...
Unused Code introduced by
The parameter $options 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

186
    protected function printScalar(ScalarType $type, /** @scrutinizer ignore-unused */ array $options): string

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...
187
    {
188
189
    }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return string. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
190
191
    /**
192
     * @param NamedTypeInterface $type
193
     * @param array              $options
194
     * @param string             $indentation
195
     * @param bool               $isFirstInBlock
196
     * @return string
197
     */
198
    protected function printDescription(
199
        NamedTypeInterface $type,
200
        array $options,
201
        string $indentation = '',
202
        bool $isFirstInBlock = true
203
    ): string {
204
        if (!($type instanceof DescriptionAwareInterface)) {
205
            return '';
206
        }
207
208
        $lines      = descriptionLines($type->getDescription(), 120 - \strlen($indentation));
209
        $linesCount = \count($lines);
210
211
        if (isset($options['commentDescriptions'])) {
212
            return $this->printDescriptionWithComments($lines, $indentation, $isFirstInBlock);
213
        }
214
215
        $description = $indentation && !$isFirstInBlock
216
            ? "\n" . $indentation . '"""'
217
            : $indentation . '"""';
218
219
        // In some circumstances, a single line can be used for the description.
220
        if (
221
            $linesCount === 1 &&
222
            ($firstLineLength = \strlen($lines[0])) < 70 &&
223
            $lines[0][$firstLineLength - 1] !== '"'
224
        ) {
225
            return $description . escapeQuote($lines[0]) . '"""' . "\n";
226
        }
227
228
        // Format a multi-line block quote to account for leading space.
229
        $hasLeadingSpace = $lines[0][0] === ' ' || $lines[0][0] === "\t";
230
        if (!$hasLeadingSpace) {
231
            $description .= "\n";
232
        }
233
234
        for ($i = 0; $i < $linesCount; $i++) {
235
            $description .= $i !== 0 || !$hasLeadingSpace
236
                ? $description .= $indentation
237
                : escapeQuote($lines[$i]) . "\n";
238
        }
239
240
        $description .= $indentation . '"""' . "\n";
241
242
        return $description;
243
    }
244
245
    protected function printDescriptionWithComments(array $lines, string $indentation, bool $isFirstInBlock): string
246
    {
247
        $description = $indentation && !$isFirstInBlock ? "\n" : '';
248
        $linesCount  = \count($lines);
249
250
        for ($i = 0; $i < $linesCount; $i++) {
251
            $description .= $lines[$i] === ''
252
                ? $indentation . '#' . "\n"
253
                : $indentation . '# ' . $lines[$i] . "\n";
254
        }
255
256
        return $description;
257
    }
258
259
    /**
260
     * @param DefinitionInterface $definition
261
     * @param array               $options
262
     * @return string
263
     * @throws PrintException
264
     */
265
    protected function printOne(DefinitionInterface $definition, array $options): string
266
    {
267
        return $this->print($definition, $options);
268
    }
269
270
    /**
271
     * @param DefinitionInterface[] $definitions
272
     * @param array                 $options
273
     * @return array
274
     */
275
    protected function printMany(array $definitions, array $options): array
276
    {
277
        return \array_map(function ($definition) use ($options) {
278
            return $this->print($definition, $options);
279
        }, $definitions);
280
    }
281
}
282