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

GraphQLRuntime::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 9.4285
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
    private function __construct()
118
    {
119
        $this->container = new Container();
120
121
        $this->registerBindings();
122
    }
123
124
    /**
125
     * @return GraphQLRuntime
126
     */
127
    public static function getInstance()
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
     * @return mixed
225
     */
226
    public static function make(string $id)
227
    {
228
        return self::getInstance()->getContainer()->get($id);
229
    }
230
231
    /**
232
     * @param string $id
233
     * @param mixed  $concrete
234
     * @return DefinitionInterface
235
     */
236
    public function bind(string $id, $concrete): DefinitionInterface
237
    {
238
        return $this->container->add($id, $concrete);
239
    }
240
241
    /**
242
     * @param string $id
243
     * @param mixed  $concrete
244
     * @return DefinitionInterface
245
     */
246
    public function singleton(string $id, $concrete): DefinitionInterface
247
    {
248
        return $this->container->add($id, $concrete, true);
249
    }
250
251
    /**
252
     * @return Container
253
     */
254
    public function getContainer(): Container
255
    {
256
        return $this->container;
257
    }
258
259
    /**
260
     *
261
     */
262
    protected function registerBindings()
263
    {
264
        $this->registerParser();
265
        $this->registerPrinter();
266
        $this->registerScalarTypes();
267
        $this->registerDirectives();
268
    }
269
270
    /**
271
     * Registers the GraphQL parser with the container.
272
     */
273
    protected function registerParser()
274
    {
275
        $this->singleton(NodeBuilderInterface::class, function () {
276
            return new NodeBuilder(self::getNodeBuilders());
277
        });
278
279
        $this->singleton(ParserInterface::class, Parser::class)
280
            ->addArgument(NodeBuilderInterface::class);
281
282
        $this->bind(LexerInterface::class, function () {
283
            return new Lexer(self::getSourceReaders());
284
        });
285
    }
286
287
    /**
288
     * Registers the GraphQL printer with the container.
289
     */
290
    protected function registerPrinter()
291
    {
292
        $this->singleton(PrinterInterface::class, Printer::class);
293
    }
294
295
    /**
296
     * Registers the GraphQL scalar types with the container.
297
     */
298
    protected function registerScalarTypes()
299
    {
300
        /**
301
         * @param $value
302
         * @return bool
303
         * @throws \TypeError
304
         */
305
        function coerceBoolean($value): bool
306
        {
307
            if (!is_scalar($value)) {
308
                throw new \TypeError(sprintf('Boolean cannot represent a non-scalar value: %s', $value));
309
            }
310
311
            return (bool)$value;
312
        }
313
314
        $this->singleton('GraphQLBoolean', function () {
315
            return GraphQLScalarType([
316
                'name'        => TypeNameEnum::BOOLEAN,
317
                'description' => 'The `Boolean` scalar type represents `true` or `false`.',
318
                'serialize'   => function ($value) {
319
                    return coerceBoolean($value);
320
                },
321
                'parseValue'  => function ($value) {
322
                    return coerceBoolean($value);
323
                },
324
325
                'parseLiteral' => function (NodeInterface $astNode) {
326
                    /** @var BooleanValueNode $astNode */
327
                    return $astNode->getKind() === NodeKindEnum::BOOLEAN ? $astNode->getValue() : null;
328
                },
329
            ]);
330
        });
331
332
        /**
333
         * @param $value
334
         * @return float
335
         * @throws \TypeError
336
         */
337
        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...
338
        {
339
            if ($value === '') {
340
                throw new \TypeError('Float cannot represent non numeric value: (empty string)');
341
            }
342
343
            if (is_numeric($value) || \is_bool($value)) {
344
                return (float)$value;
345
            }
346
347
            throw new \TypeError(sprintf('Float cannot represent non numeric value: %s', $value));
348
        }
349
350
        $this->singleton('GraphQLFloat', function () {
351
            return GraphQLScalarType([
352
                'name'         => TypeNameEnum::FLOAT,
353
                'description'  =>
354
                    'The `Float` scalar type represents signed double-precision fractional ' .
355
                    'values as specified by ' .
356
                    '[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).',
357
                'serialize'    => function ($value) {
358
                    return coerceFloat($value);
359
                },
360
                'parseValue'   => function ($value) {
361
                    return coerceFloat($value);
362
                },
363
                'parseLiteral' => function (NodeInterface $astNode) {
364
                    /** @var FloatValueNode $astNode */
365
                    return in_array($astNode->getKind(), [NodeKindEnum::FLOAT, NodeKindEnum::INT], true)
366
                        ? $astNode->getValue()
367
                        : null;
368
                },
369
            ]);
370
        });
371
372
        /**
373
         * @param $value
374
         * @return int
375
         * @throws \TypeError
376
         */
377
        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...
378
        {
379
            if ($value === '') {
380
                throw new \TypeError('Int cannot represent non 32-bit signed integer value: (empty string)');
381
            }
382
383
            if (\is_bool($value)) {
384
                $value = (int)$value;
385
            }
386
387
            if (!\is_int($value) || $value > MAX_INT || $value < MIN_INT) {
388
                throw new \TypeError(sprintf('Int cannot represent non 32-bit signed integer value: %s', $value));
389
            }
390
391
            $intValue   = (int)$value;
392
            $floatValue = (float)$value;
393
394
            if ($floatValue != $intValue || floor($floatValue) !== $floatValue) {
395
                throw new \TypeError(sprintf('Int cannot represent non-integer value: %s', $value));
396
            }
397
398
            return $intValue;
399
        }
400
401
        $this->singleton('GraphQLInt', function () {
402
            return GraphQLScalarType([
403
                'name'         => TypeNameEnum::INT,
404
                'description'  =>
405
                    'The `Int` scalar type represents non-fractional signed whole numeric ' .
406
                    'values. Int can represent values between -(2^31) and 2^31 - 1.',
407
                'serialize'    => function ($value) {
408
                    return coerceInt($value);
409
                },
410
                'parseValue'   => function ($value) {
411
                    return coerceInt($value);
412
                },
413
                'parseLiteral' => function (NodeInterface $astNode) {
414
                    /** @var IntValueNode $astNode */
415
                    return $astNode->getKind() === NodeKindEnum::INT ? $astNode->getValue() : null;
416
                },
417
            ]);
418
        });
419
420
        /**
421
         * @param $value
422
         * @return string
423
         * @throws \TypeError
424
         */
425
        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...
426
        {
427
            if ($value === null) {
428
                return 'null';
429
            }
430
431
            if ($value === true) {
432
                return 'true';
433
            }
434
435
            if ($value === false) {
436
                return 'false';
437
            }
438
439
            if (!is_scalar($value)) {
440
                throw new \TypeError('String cannot represent a non-scalar value');
441
            }
442
443
            return (string)$value;
444
        }
445
446
        $this->singleton('GraphQLID', function () {
447
            return GraphQLScalarType([
448
                'name'         => TypeNameEnum::ID,
449
                'description'  =>
450
                    'The `ID` scalar type represents a unique identifier, often used to ' .
451
                    'refetch an object or as key for a cache. The ID type appears in a JSON ' .
452
                    'response as a String; however, it is not intended to be human-readable. ' .
453
                    'When expected as an input type, any string (such as `"4"`) or integer ' .
454
                    '(such as `4`) input value will be accepted as an ID.',
455
                'serialize'    => function ($value) {
456
                    return coerceString($value);
457
                },
458
                'parseValue'   => function ($value) {
459
                    return coerceString($value);
460
                },
461
                'parseLiteral' => function (NodeInterface $astNode) {
462
                    /** @var StringValueNode $astNode */
463
                    return in_array($astNode->getKind(), [NodeKindEnum::STRING, NodeKindEnum::INT], true)
464
                        ? $astNode->getValue()
465
                        : null;
466
                },
467
            ]);
468
        });
469
470
        $this->singleton('GraphQLString', function () {
471
            return GraphQLScalarType([
472
                'name'         => TypeNameEnum::STRING,
473
                'description'  =>
474
                    'The `String` scalar type represents textual data, represented as UTF-8 ' .
475
                    'character sequences. The String type is most often used by GraphQL to ' .
476
                    'represent free-form human-readable text.',
477
                'serialize'    => function ($value) {
478
                    return coerceString($value);
479
                },
480
                'parseValue'   => function ($value) {
481
                    return coerceString($value);
482
                },
483
                'parseLiteral' => function (NodeInterface $astNode) {
484
                    /** @var StringValueNode $astNode */
485
                    return $astNode->getKind() === NodeKindEnum::STRING ? $astNode->getValue() : null;
486
                },
487
            ]);
488
        });
489
    }
490
491
    /**
492
     * Registers the GraphQL directives with the container.
493
     */
494
    protected function registerDirectives()
495
    {
496
        $this->singleton('GraphQLIncludeDirective', function () {
497
            return GraphQLDirective([
498
                'name'        => 'include',
499
                'description' =>
500
                    'Directs the executor to include this field or fragment only when ' .
501
                    'the `if` argument is true.',
502
                'locations'   => [
503
                    DirectiveLocationEnum::FIELD,
504
                    DirectiveLocationEnum::FRAGMENT_SPREAD,
505
                    DirectiveLocationEnum::INLINE_FRAGMENT,
506
                ],
507
                'args'        => [
508
                    'if ' => [
509
                        'type'        => GraphQLNonNull(GraphQLBoolean()),
510
                        'description' => 'Included when true.',
511
                    ],
512
                ],
513
            ]);
514
        });
515
516
        $this->singleton('GraphQLSkipDirective', function () {
517
            return GraphQLDirective([
518
                'name'        => 'skip',
519
                'description' =>
520
                    'Directs the executor to skip this field or fragment when the `if` ' .
521
                    'argument is true.',
522
                'locations'   => [
523
                    DirectiveLocationEnum::FIELD,
524
                    DirectiveLocationEnum::FRAGMENT_SPREAD,
525
                    DirectiveLocationEnum::INLINE_FRAGMENT,
526
                ],
527
                'args'        => [
528
                    'if' => [
529
                        'type'        => GraphQLNonNull(GraphQLBoolean()),
530
                        'description' => 'Skipped when true.',
531
                    ],
532
                ],
533
            ]);
534
        });
535
536
        $this->singleton('GraphQLDeprecatedDirective', function () {
537
            return GraphQLDirective([
538
                'name'        => 'deprecated',
539
                'description' => 'Marks an element of a GraphQL schema as no longer supported.',
540
                'locations'   => [
541
                    DirectiveLocationEnum::FIELD_DEFINITION,
542
                    DirectiveLocationEnum::ENUM_VALUE,
543
                ],
544
                'args'        => [
545
                    'reason' => [
546
                        'type'         => GraphQLString(),
547
                        'description'  =>
548
                            'Explains why this element was deprecated, usually also including a ' .
549
                            'suggestion for how to access supported similar data. Formatted ' .
550
                            'in [Markdown](https://daringfireball.net/projects/markdown/).',
551
                        'defaultValue' => DEFAULT_DEPRECATION_REASON,
552
                    ],
553
                ]
554
            ]);
555
        });
556
    }
557
}
558