Passed
Pull Request — master (#21)
by Christoffer
02:03
created

GraphQLRuntime   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 453
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 453
rs 10
c 0
b 0
f 0
wmc 21

16 Methods

Rating   Name   Duplication   Size   Complexity  
B getSourceReaders() 0 24 2
A coerceFloat() 0 11 4
C registerScalarTypes() 0 188 7
A __construct() 0 5 1
A bind() 0 3 1
A registerParser() 0 11 1
C coerceInt() 0 22 8
A make() 0 3 1
A getNodeBuilders() 0 52 2
A singleton() 0 3 1
B coerceString() 0 19 5
A get() 0 7 2
A coerceBoolean() 0 7 2
A registerBindings() 0 6 1
A registerDirectives() 0 58 1
A registerPrinter() 0 3 1
1
<?php
2
3
namespace Digia\GraphQL;
4
5
use Digia\GraphQL\Language\AST\Builder\ArgumentBuilder;
6
use Digia\GraphQL\Language\AST\Builder\BooleanBuilder;
7
use Digia\GraphQL\Language\AST\Builder\BuilderInterface;
8
use Digia\GraphQL\Language\AST\Builder\DirectiveBuilder;
9
use Digia\GraphQL\Language\AST\Builder\DirectiveDefinitionBuilder;
10
use Digia\GraphQL\Language\AST\Builder\DocumentBuilder;
11
use Digia\GraphQL\Language\AST\Builder\EnumBuilder;
12
use Digia\GraphQL\Language\AST\Builder\EnumTypeDefinitionBuilder;
13
use Digia\GraphQL\Language\AST\Builder\EnumTypeExtensionBuilder;
14
use Digia\GraphQL\Language\AST\Builder\EnumValueDefinitionBuilder;
15
use Digia\GraphQL\Language\AST\Builder\FieldBuilder;
16
use Digia\GraphQL\Language\AST\Builder\FieldDefinitionBuilder;
17
use Digia\GraphQL\Language\AST\Builder\FloatBuilder;
18
use Digia\GraphQL\Language\AST\Builder\FragmentDefinitionBuilder;
19
use Digia\GraphQL\Language\AST\Builder\FragmentSpreadBuilder;
20
use Digia\GraphQL\Language\AST\Builder\InlineFragmentBuilder;
21
use Digia\GraphQL\Language\AST\Builder\InputObjectTypeDefinitionBuilder;
22
use Digia\GraphQL\Language\AST\Builder\InputObjectTypeExtensionBuilder;
23
use Digia\GraphQL\Language\AST\Builder\InputValueDefinitionBuilder;
24
use Digia\GraphQL\Language\AST\Builder\IntBuilder;
25
use Digia\GraphQL\Language\AST\Builder\InterfaceTypeDefinitionBuilder;
26
use Digia\GraphQL\Language\AST\Builder\InterfaceTypeExtensionBuilder;
27
use Digia\GraphQL\Language\AST\Builder\ListBuilder;
28
use Digia\GraphQL\Language\AST\Builder\ListTypeBuilder;
29
use Digia\GraphQL\Language\AST\Builder\NameBuilder;
30
use Digia\GraphQL\Language\AST\Builder\NamedTypeBuilder;
31
use Digia\GraphQL\Language\AST\Builder\NodeBuilder;
32
use Digia\GraphQL\Language\AST\Builder\NodeBuilderInterface;
33
use Digia\GraphQL\Language\AST\Builder\NonNullTypeBuilder;
34
use Digia\GraphQL\Language\AST\Builder\NullBuilder;
35
use Digia\GraphQL\Language\AST\Builder\ObjectBuilder;
36
use Digia\GraphQL\Language\AST\Builder\ObjectFieldBuilder;
37
use Digia\GraphQL\Language\AST\Builder\ObjectTypeDefinitionBuilder;
38
use Digia\GraphQL\Language\AST\Builder\ObjectTypeExtensionBuilder;
39
use Digia\GraphQL\Language\AST\Builder\OperationDefinitionBuilder;
40
use Digia\GraphQL\Language\AST\Builder\OperationTypeDefinitionBuilder;
41
use Digia\GraphQL\Language\AST\Builder\ScalarTypeDefinitionBuilder;
42
use Digia\GraphQL\Language\AST\Builder\ScalarTypeExtensionBuilder;
43
use Digia\GraphQL\Language\AST\Builder\SchemaDefinitionBuilder;
44
use Digia\GraphQL\Language\AST\Builder\SelectionSetBuilder;
45
use Digia\GraphQL\Language\AST\Builder\StringBuilder;
46
use Digia\GraphQL\Language\AST\Builder\UnionTypeDefinitionBuilder;
47
use Digia\GraphQL\Language\AST\Builder\UnionTypeExtensionBuilder;
48
use Digia\GraphQL\Language\AST\Builder\VariableBuilder;
49
use Digia\GraphQL\Language\AST\Builder\VariableDefinitionBuilder;
50
use Digia\GraphQL\Language\AST\DirectiveLocationEnum;
51
use Digia\GraphQL\Language\AST\Node\BooleanValueNode;
52
use Digia\GraphQL\Language\AST\Node\FloatValueNode;
53
use Digia\GraphQL\Language\AST\Node\IntValueNode;
54
use Digia\GraphQL\Language\AST\Node\NodeInterface;
55
use Digia\GraphQL\Language\AST\Node\StringValueNode;
56
use Digia\GraphQL\Language\AST\NodeKindEnum;
57
use Digia\GraphQL\Language\Lexer;
58
use Digia\GraphQL\Language\LexerInterface;
59
use Digia\GraphQL\Language\Parser;
60
use Digia\GraphQL\Language\ParserInterface;
61
use Digia\GraphQL\Language\Printer;
62
use Digia\GraphQL\Language\PrinterInterface;
63
use Digia\GraphQL\Language\Reader\AmpReader;
64
use Digia\GraphQL\Language\Reader\AtReader;
65
use Digia\GraphQL\Language\Reader\BangReader;
66
use Digia\GraphQL\Language\Reader\BlockStringReader;
67
use Digia\GraphQL\Language\Reader\BraceReader;
68
use Digia\GraphQL\Language\Reader\BracketReader;
69
use Digia\GraphQL\Language\Reader\ColonReader;
70
use Digia\GraphQL\Language\Reader\CommentReader;
71
use Digia\GraphQL\Language\Reader\DollarReader;
72
use Digia\GraphQL\Language\Reader\EqualsReader;
73
use Digia\GraphQL\Language\Reader\NameReader;
74
use Digia\GraphQL\Language\Reader\NumberReader;
75
use Digia\GraphQL\Language\Reader\ParenthesisReader;
76
use Digia\GraphQL\Language\Reader\PipeReader;
77
use Digia\GraphQL\Language\Reader\ReaderInterface;
78
use Digia\GraphQL\Language\Reader\SpreadReader;
79
use Digia\GraphQL\Language\Reader\StringReader;
80
use Digia\GraphQL\Type\Definition\TypeNameEnum;
81
use League\Container\Container;
82
use League\Container\Definition\DefinitionInterface;
83
use const Digia\GraphQL\Type\MAX_INT;
0 ignored issues
show
Bug introduced by
The constant Digia\GraphQL\Type\MAX_INT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
84
use const Digia\GraphQL\Type\MIN_INT;
0 ignored issues
show
Bug introduced by
The constant Digia\GraphQL\Type\MIN_INT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
85
use function Digia\GraphQL\Type\GraphQLBoolean;
86
use function Digia\GraphQL\Type\GraphQLDirective;
87
use function Digia\GraphQL\Type\GraphQLNonNull;
88
use function Digia\GraphQL\Type\GraphQLScalarType;
89
use function Digia\GraphQL\Type\GraphQLString;
90
91
class GraphQLRuntime
92
{
93
94
    /**
95
     * @var GraphQLRuntime
96
     */
97
    private static $instance;
98
99
    /**
100
     * @var BuilderInterface[]
101
     */
102
    private static $nodeBuilders;
103
104
    /**
105
     * @var ReaderInterface[]
106
     */
107
    private static $sourceReaders;
108
109
    /**
110
     * @var Container
111
     */
112
    protected $container;
113
114
    /**
115
     * GraphQL constructor.
116
     */
117
    public function __construct()
118
    {
119
        $this->container = new Container();
120
121
        $this->registerBindings();
122
    }
123
124
    /**
125
     * @return GraphQLRuntime
126
     */
127
    public static function get()
128
    {
129
        if (null === self::$instance) {
130
            self::$instance = new static();
131
        }
132
133
        return self::$instance;
134
    }
135
136
    /**
137
     * @return BuilderInterface[]
138
     */
139
    public static function getNodeBuilders(): array
140
    {
141
        if (null === self::$nodeBuilders) {
0 ignored issues
show
introduced by
The condition null === self::nodeBuilders can never be true.
Loading history...
142
            self::$nodeBuilders = [
143
                // Standard
144
                new ArgumentBuilder(),
145
                new BooleanBuilder(),
146
                new DirectiveBuilder(),
147
                new DocumentBuilder(),
148
                new EnumBuilder(),
149
                new FieldBuilder(),
150
                new FloatBuilder(),
151
                new FragmentDefinitionBuilder(),
152
                new FragmentSpreadBuilder(),
153
                new InlineFragmentBuilder(),
154
                new IntBuilder(),
155
                new ListBuilder(),
156
                new ListTypeBuilder(),
157
                new NameBuilder(),
158
                new NamedTypeBuilder(),
159
                new NonNullTypeBuilder(),
160
                new NullBuilder(),
161
                new ObjectBuilder(),
162
                new ObjectFieldBuilder(),
163
                new OperationDefinitionBuilder(),
164
                new SelectionSetBuilder(),
165
                new StringBuilder(),
166
                new VariableBuilder(),
167
                new VariableDefinitionBuilder(),
168
                // Schema Definition Language (SDL)
169
                new SchemaDefinitionBuilder(),
170
                new OperationTypeDefinitionBuilder(),
171
                new FieldDefinitionBuilder(),
172
                new ScalarTypeDefinitionBuilder(),
173
                new ObjectTypeDefinitionBuilder(),
174
                new InterfaceTypeDefinitionBuilder(),
175
                new UnionTypeDefinitionBuilder(),
176
                new EnumTypeDefinitionBuilder(),
177
                new EnumValueDefinitionBuilder(),
178
                new InputObjectTypeDefinitionBuilder(),
179
                new InputValueDefinitionBuilder(),
180
                new ScalarTypeExtensionBuilder(),
181
                new ObjectTypeExtensionBuilder(),
182
                new InterfaceTypeExtensionBuilder(),
183
                new EnumTypeExtensionBuilder(),
184
                new UnionTypeExtensionBuilder(),
185
                new InputObjectTypeExtensionBuilder(),
186
                new DirectiveDefinitionBuilder(),
187
            ];
188
        }
189
190
        return self::$nodeBuilders;
191
    }
192
193
    /**
194
     * @return ReaderInterface[]
195
     */
196
    public static function getSourceReaders(): array
197
    {
198
        if (null === self::$sourceReaders) {
0 ignored issues
show
introduced by
The condition null === self::sourceReaders can never be true.
Loading history...
199
            self::$sourceReaders = [
200
                new AmpReader(),
201
                new AtReader(),
202
                new BangReader(),
203
                new BlockStringReader(),
204
                new BraceReader(),
205
                new BracketReader(),
206
                new ColonReader(),
207
                new CommentReader(),
208
                new DollarReader(),
209
                new EqualsReader(),
210
                new NameReader(),
211
                new NumberReader(),
212
                new ParenthesisReader(),
213
                new PipeReader(),
214
                new SpreadReader(),
215
                new StringReader(),
216
            ];
217
        }
218
219
        return self::$sourceReaders;
220
    }
221
222
    /**
223
     * @param string $id
224
     * @param mixed  $concrete
225
     * @return DefinitionInterface
226
     */
227
    public function bind(string $id, $concrete): DefinitionInterface
228
    {
229
        return $this->container->add($id, $concrete);
230
    }
231
232
    /**
233
     * @param string $id
234
     * @param mixed  $concrete
235
     * @return DefinitionInterface
236
     */
237
    public function singleton(string $id, $concrete): DefinitionInterface
238
    {
239
        return $this->container->add($id, $concrete, true);
240
    }
241
242
    /**
243
     * @param string $id
244
     * @return mixed
245
     */
246
    public function make(string $id)
247
    {
248
        return $this->container->get($id);
249
    }
250
251
    /**
252
     *
253
     */
254
    protected function registerBindings()
255
    {
256
        $this->registerParser();
257
        $this->registerPrinter();
258
        $this->registerScalarTypes();
259
        $this->registerDirectives();
260
    }
261
262
    /**
263
     * Registers the GraphQL parser with the container.
264
     */
265
    protected function registerParser()
266
    {
267
        $this->singleton(NodeBuilderInterface::class, function () {
268
            return new NodeBuilder(self::getNodeBuilders());
269
        });
270
271
        $this->singleton(ParserInterface::class, Parser::class)
272
            ->addArgument(NodeBuilderInterface::class);
273
274
        $this->bind(LexerInterface::class, function () {
275
            return new Lexer(self::getSourceReaders());
276
        });
277
    }
278
279
    /**
280
     * Registers the GraphQL printer with the container.
281
     */
282
    protected function registerPrinter()
283
    {
284
        $this->singleton(PrinterInterface::class, Printer::class);
285
    }
286
287
    /**
288
     * Registers the GraphQL scalar types with the container.
289
     */
290
    protected function registerScalarTypes()
291
    {
292
        /**
293
         * @param $value
294
         * @return bool
295
         * @throws \TypeError
296
         */
297
        function coerceBoolean($value): bool
298
        {
299
            if (!is_scalar($value)) {
300
                throw new \TypeError(sprintf('Boolean cannot represent a non-scalar value: %s', $value));
301
            }
302
303
            return (bool)$value;
304
        }
305
306
        $this->singleton('GraphQLBoolean', function () {
307
            return GraphQLScalarType([
308
                'name'        => TypeNameEnum::BOOLEAN,
309
                'description' => 'The `Boolean` scalar type represents `true` or `false`.',
310
                'serialize'   => function ($value) {
311
                    return coerceBoolean($value);
312
                },
313
                'parseValue'  => function ($value) {
314
                    return coerceBoolean($value);
315
                },
316
317
                'parseLiteral' => function (NodeInterface $astNode) {
318
                    /** @var BooleanValueNode $astNode */
319
                    return $astNode->getKind() === NodeKindEnum::BOOLEAN ? $astNode->getValue() : null;
320
                },
321
            ]);
322
        });
323
324
        /**
325
         * @param $value
326
         * @return float
327
         * @throws \TypeError
328
         */
329
        function coerceFloat($value): float
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
330
        {
331
            if ($value === '') {
332
                throw new \TypeError('Float cannot represent non numeric value: (empty string)');
333
            }
334
335
            if (is_numeric($value) || \is_bool($value)) {
336
                return (float)$value;
337
            }
338
339
            throw new \TypeError(sprintf('Float cannot represent non numeric value: %s', $value));
340
        }
341
342
        $this->singleton('GraphQLFloat', function () {
343
            return GraphQLScalarType([
344
                'name'         => TypeNameEnum::FLOAT,
345
                'description'  =>
346
                    'The `Float` scalar type represents signed double-precision fractional ' .
347
                    'values as specified by ' .
348
                    '[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).',
349
                'serialize'    => function ($value) {
350
                    return coerceFloat($value);
351
                },
352
                'parseValue'   => function ($value) {
353
                    return coerceFloat($value);
354
                },
355
                'parseLiteral' => function (NodeInterface $astNode) {
356
                    /** @var FloatValueNode $astNode */
357
                    return in_array($astNode->getKind(), [NodeKindEnum::FLOAT, NodeKindEnum::INT], true)
358
                        ? $astNode->getValue()
359
                        : null;
360
                },
361
            ]);
362
        });
363
364
        /**
365
         * @param $value
366
         * @return int
367
         * @throws \TypeError
368
         */
369
        function coerceInt($value)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
370
        {
371
            if ($value === '') {
372
                throw new \TypeError('Int cannot represent non 32-bit signed integer value: (empty string)');
373
            }
374
375
            if (\is_bool($value)) {
376
                $value = (int)$value;
377
            }
378
379
            if (!\is_int($value) || $value > MAX_INT || $value < MIN_INT) {
380
                throw new \TypeError(sprintf('Int cannot represent non 32-bit signed integer value: %s', $value));
381
            }
382
383
            $intValue   = (int)$value;
384
            $floatValue = (float)$value;
385
386
            if ($floatValue != $intValue || floor($floatValue) !== $floatValue) {
387
                throw new \TypeError(sprintf('Int cannot represent non-integer value: %s', $value));
388
            }
389
390
            return $intValue;
391
        }
392
393
        $this->singleton('GraphQLInt', function () {
394
            return GraphQLScalarType([
395
                'name'         => TypeNameEnum::INT,
396
                'description'  =>
397
                    'The `Int` scalar type represents non-fractional signed whole numeric ' .
398
                    'values. Int can represent values between -(2^31) and 2^31 - 1.',
399
                'serialize'    => function ($value) {
400
                    return coerceInt($value);
401
                },
402
                'parseValue'   => function ($value) {
403
                    return coerceInt($value);
404
                },
405
                'parseLiteral' => function (NodeInterface $astNode) {
406
                    /** @var IntValueNode $astNode */
407
                    return $astNode->getKind() === NodeKindEnum::INT ? $astNode->getValue() : null;
408
                },
409
            ]);
410
        });
411
412
        /**
413
         * @param $value
414
         * @return string
415
         * @throws \TypeError
416
         */
417
        function coerceString($value): string
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
418
        {
419
            if ($value === null) {
420
                return 'null';
421
            }
422
423
            if ($value === true) {
424
                return 'true';
425
            }
426
427
            if ($value === false) {
428
                return 'false';
429
            }
430
431
            if (!is_scalar($value)) {
432
                throw new \TypeError('String cannot represent a non-scalar value');
433
            }
434
435
            return (string)$value;
436
        }
437
438
        $this->singleton('GraphQLID', function () {
439
            return GraphQLScalarType([
440
                'name'         => TypeNameEnum::ID,
441
                'description'  =>
442
                    'The `ID` scalar type represents a unique identifier, often used to ' .
443
                    'refetch an object or as key for a cache. The ID type appears in a JSON ' .
444
                    'response as a String; however, it is not intended to be human-readable. ' .
445
                    'When expected as an input type, any string (such as `"4"`) or integer ' .
446
                    '(such as `4`) input value will be accepted as an ID.',
447
                'serialize'    => function ($value) {
448
                    return coerceString($value);
449
                },
450
                'parseValue'   => function ($value) {
451
                    return coerceString($value);
452
                },
453
                'parseLiteral' => function (NodeInterface $astNode) {
454
                    /** @var StringValueNode $astNode */
455
                    return in_array($astNode->getKind(), [NodeKindEnum::STRING, NodeKindEnum::INT], true)
456
                        ? $astNode->getValue()
457
                        : null;
458
                },
459
            ]);
460
        });
461
462
        $this->singleton('GraphQLString', function () {
463
            return GraphQLScalarType([
464
                'name'         => TypeNameEnum::STRING,
465
                'description'  =>
466
                    'The `String` scalar type represents textual data, represented as UTF-8 ' .
467
                    'character sequences. The String type is most often used by GraphQL to ' .
468
                    'represent free-form human-readable text.',
469
                'serialize'    => function ($value) {
470
                    return coerceString($value);
471
                },
472
                'parseValue'   => function ($value) {
473
                    return coerceString($value);
474
                },
475
                'parseLiteral' => function (NodeInterface $astNode) {
476
                    /** @var StringValueNode $astNode */
477
                    return $astNode->getKind() === NodeKindEnum::STRING ? $astNode->getValue() : null;
478
                },
479
            ]);
480
        });
481
    }
482
483
    /**
484
     * Registers the GraphQL directives with the container.
485
     */
486
    protected function registerDirectives()
487
    {
488
        $this->singleton('GraphQLIncludeDirective', function () {
489
            return GraphQLDirective([
490
                'name'        => 'include',
491
                'description' =>
492
                    'Directs the executor to include this field or fragment only when ' .
493
                    'the `if` argument is true.',
494
                'locations'   => [
495
                    DirectiveLocationEnum::FIELD,
496
                    DirectiveLocationEnum::FRAGMENT_SPREAD,
497
                    DirectiveLocationEnum::INLINE_FRAGMENT,
498
                ],
499
                'args'        => [
500
                    'if ' => [
501
                        'type'        => GraphQLNonNull(GraphQLBoolean()),
502
                        'description' => 'Included when true.',
503
                    ],
504
                ],
505
            ]);
506
        });
507
508
        $this->singleton('GraphQLSkipDirective', function () {
509
            return GraphQLDirective([
510
                'name'        => 'skip',
511
                'description' =>
512
                    'Directs the executor to skip this field or fragment when the `if` ' .
513
                    'argument is true.',
514
                'locations'   => [
515
                    DirectiveLocationEnum::FIELD,
516
                    DirectiveLocationEnum::FRAGMENT_SPREAD,
517
                    DirectiveLocationEnum::INLINE_FRAGMENT,
518
                ],
519
                'args'        => [
520
                    'if' => [
521
                        'type'        => GraphQLNonNull(GraphQLBoolean()),
522
                        'description' => 'Skipped when true.',
523
                    ],
524
                ],
525
            ]);
526
        });
527
528
        $this->singleton('GraphQLDeprecatedDirective', function () {
529
            return GraphQLDirective([
530
                'name'        => 'deprecated',
531
                'description' => 'Marks an element of a GraphQL schema as no longer supported.',
532
                'locations'   => [
533
                    DirectiveLocationEnum::FIELD_DEFINITION,
534
                    DirectiveLocationEnum::ENUM_VALUE,
535
                ],
536
                'args'        => [
537
                    'reason' => [
538
                        'type'         => GraphQLString(),
539
                        'description'  =>
540
                            'Explains why this element was deprecated, usually also including a ' .
541
                            'suggestion for how to access supported similar data. Formatted ' .
542
                            'in [Markdown](https://daringfireball.net/projects/markdown/).',
543
                        'defaultValue' => DEFAULT_DEPRECATION_REASON,
544
                    ],
545
                ]
546
            ]);
547
        });
548
    }
549
}
550