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

GraphQLRuntime::shared()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
            foreach (self::$supportedNodeBuilders as $className) {
143
                self::$nodeBuilders[] = new $className();
144
            }
145
        }
146
147
        return self::$nodeBuilders;
148
    }
149
150
    /**
151
     * @return ReaderInterface[]
152
     */
153
    public static function getSourceReaders(): array
154
    {
155
        if (null === self::$sourceReaders) {
0 ignored issues
show
introduced by
The condition null === self::sourceReaders can never be true.
Loading history...
156
            foreach (self::$supportedSourceReaders as $className) {
157
                self::$sourceReaders[] = new $className();
158
            }
159
        }
160
161
        return self::$sourceReaders;
162
    }
163
164
    /**
165
     * @param string $id
166
     * @return mixed
167
     */
168
    public static function get(string $id)
169
    {
170
        return self::getInstance()->getContainer()->get($id);
171
    }
172
173
    /**
174
     * @param string $id
175
     * @param mixed $concrete
176
     * @return DefinitionInterface
177
     */
178
    public function bind(string $id, $concrete): DefinitionInterface
179
    {
180
        return $this->container->add($id, $concrete);
181
    }
182
183
    /**
184
     * @param string $id
185
     * @param mixed $concrete
186
     * @return DefinitionInterface
187
     */
188
    public function shared(string $id, $concrete): DefinitionInterface
189
    {
190
        return $this->container->add($id, $concrete, true);
191
    }
192
193
    /**
194
     * @return Container
195
     */
196
    public function getContainer(): Container
197
    {
198
        return $this->container;
199
    }
200
201
    /**
202
     *
203
     */
204
    protected function registerBindings()
205
    {
206
        $this->registerParser();
207
        $this->registerPrinter();
208
        $this->registerScalarTypes();
209
        $this->registerDirectives();
210
    }
211
212
    /**
213
     * Registers the GraphQL parser with the container.
214
     */
215
    protected function registerParser()
216
    {
217
        $this->shared(NodeBuilderInterface::class, function () {
218
            return new NodeBuilder(self::getNodeBuilders());
219
        });
220
221
        $this->shared(ParserInterface::class, Parser::class)
222
            ->withArgument(NodeBuilderInterface::class);
223
224
        $this->bind(LexerInterface::class, function () {
225
            return new Lexer(self::getSourceReaders());
226
        });
227
    }
228
229
    /**
230
     * Registers the GraphQL printer with the container.
231
     */
232
    protected function registerPrinter()
233
    {
234
        $this->shared(PrinterInterface::class, Printer::class);
235
    }
236
237
    /**
238
     * Registers the GraphQL scalar types with the container.
239
     */
240
    protected function registerScalarTypes()
241
    {
242
        /**
243
         * @param $value
244
         * @return bool
245
         * @throws \TypeError
246
         */
247
        function coerceBoolean($value): bool
248
        {
249
            if (!is_scalar($value)) {
250
                throw new \TypeError(sprintf('Boolean cannot represent a non-scalar value: %s', $value));
251
            }
252
253
            return (bool)$value;
254
        }
255
256
        $this->shared('GraphQLBoolean', function () {
257
            return GraphQLScalarType([
258
                'name' => TypeNameEnum::BOOLEAN,
259
                'description' => 'The `Boolean` scalar type represents `true` or `false`.',
260
                'serialize' => function ($value) {
261
                    return coerceBoolean($value);
262
                },
263
                'parseValue' => function ($value) {
264
                    return coerceBoolean($value);
265
                },
266
267
                'parseLiteral' => function (NodeInterface $astNode) {
268
                    /** @var BooleanValueNode $astNode */
269
                    return $astNode->getKind() === NodeKindEnum::BOOLEAN ? $astNode->getValue() : null;
270
                },
271
            ]);
272
        });
273
274
        /**
275
         * @param $value
276
         * @return float
277
         * @throws \TypeError
278
         */
279
        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...
280
        {
281
            if ($value === '') {
282
                throw new \TypeError('Float cannot represent non numeric value: (empty string)');
283
            }
284
285
            if (is_numeric($value) || \is_bool($value)) {
286
                return (float)$value;
287
            }
288
289
            throw new \TypeError(sprintf('Float cannot represent non numeric value: %s', $value));
290
        }
291
292
        $this->shared('GraphQLFloat', function () {
293
            return GraphQLScalarType([
294
                'name' => TypeNameEnum::FLOAT,
295
                'description' =>
296
                    'The `Float` scalar type represents signed double-precision fractional ' .
297
                    'values as specified by ' .
298
                    '[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).',
299
                'serialize' => function ($value) {
300
                    return coerceFloat($value);
301
                },
302
                'parseValue' => function ($value) {
303
                    return coerceFloat($value);
304
                },
305
                'parseLiteral' => function (NodeInterface $astNode) {
306
                    /** @var FloatValueNode $astNode */
307
                    return in_array($astNode->getKind(), [NodeKindEnum::FLOAT, NodeKindEnum::INT], true)
308
                        ? $astNode->getValue()
309
                        : null;
310
                },
311
            ]);
312
        });
313
314
        /**
315
         * @param $value
316
         * @return int
317
         * @throws \TypeError
318
         */
319
        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...
320
        {
321
            if ($value === '') {
322
                throw new \TypeError('Int cannot represent non 32-bit signed integer value: (empty string)');
323
            }
324
325
            if (\is_bool($value)) {
326
                $value = (int)$value;
327
            }
328
329
            if (!\is_int($value) || $value > MAX_INT || $value < MIN_INT) {
330
                throw new \TypeError(sprintf('Int cannot represent non 32-bit signed integer value: %s', $value));
331
            }
332
333
            $intValue = (int)$value;
334
            $floatValue = (float)$value;
335
336
            if ($floatValue != $intValue || floor($floatValue) !== $floatValue) {
337
                throw new \TypeError(sprintf('Int cannot represent non-integer value: %s', $value));
338
            }
339
340
            return $intValue;
341
        }
342
343
        $this->shared('GraphQLInt', function () {
344
            return GraphQLScalarType([
345
                'name' => TypeNameEnum::INT,
346
                'description' =>
347
                    'The `Int` scalar type represents non-fractional signed whole numeric ' .
348
                    'values. Int can represent values between -(2^31) and 2^31 - 1.',
349
                'serialize' => function ($value) {
350
                    return coerceInt($value);
351
                },
352
                'parseValue' => function ($value) {
353
                    return coerceInt($value);
354
                },
355
                'parseLiteral' => function (NodeInterface $astNode) {
356
                    /** @var IntValueNode $astNode */
357
                    return $astNode->getKind() === NodeKindEnum::INT ? $astNode->getValue() : null;
358
                },
359
            ]);
360
        });
361
362
        /**
363
         * @param $value
364
         * @return string
365
         * @throws \TypeError
366
         */
367
        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...
368
        {
369
            if ($value === null) {
370
                return 'null';
371
            }
372
373
            if ($value === true) {
374
                return 'true';
375
            }
376
377
            if ($value === false) {
378
                return 'false';
379
            }
380
381
            if (!is_scalar($value)) {
382
                throw new \TypeError('String cannot represent a non-scalar value');
383
            }
384
385
            return (string)$value;
386
        }
387
388
        $this->shared('GraphQLID', function () {
389
            return GraphQLScalarType([
390
                'name' => TypeNameEnum::ID,
391
                'description' =>
392
                    'The `ID` scalar type represents a unique identifier, often used to ' .
393
                    'refetch an object or as key for a cache. The ID type appears in a JSON ' .
394
                    'response as a String; however, it is not intended to be human-readable. ' .
395
                    'When expected as an input type, any string (such as `"4"`) or integer ' .
396
                    '(such as `4`) input value will be accepted as an ID.',
397
                'serialize' => function ($value) {
398
                    return coerceString($value);
399
                },
400
                'parseValue' => function ($value) {
401
                    return coerceString($value);
402
                },
403
                'parseLiteral' => function (NodeInterface $astNode) {
404
                    /** @var StringValueNode $astNode */
405
                    return in_array($astNode->getKind(), [NodeKindEnum::STRING, NodeKindEnum::INT], true)
406
                        ? $astNode->getValue()
407
                        : null;
408
                },
409
            ]);
410
        });
411
412
        $this->shared('GraphQLString', function () {
413
            return GraphQLScalarType([
414
                'name' => TypeNameEnum::STRING,
415
                'description' =>
416
                    'The `String` scalar type represents textual data, represented as UTF-8 ' .
417
                    'character sequences. The String type is most often used by GraphQL to ' .
418
                    'represent free-form human-readable text.',
419
                'serialize' => function ($value) {
420
                    return coerceString($value);
421
                },
422
                'parseValue' => function ($value) {
423
                    return coerceString($value);
424
                },
425
                'parseLiteral' => function (NodeInterface $astNode) {
426
                    /** @var StringValueNode $astNode */
427
                    return $astNode->getKind() === NodeKindEnum::STRING ? $astNode->getValue() : null;
428
                },
429
            ]);
430
        });
431
    }
432
433
    /**
434
     * Registers the GraphQL directives with the container.
435
     */
436
    protected function registerDirectives()
437
    {
438
        $this->shared('GraphQLIncludeDirective', function () {
439
            return GraphQLDirective([
440
                'name' => 'include',
441
                'description' =>
442
                    'Directs the executor to include this field or fragment only when ' .
443
                    'the `if` argument is true.',
444
                'locations' => [
445
                    DirectiveLocationEnum::FIELD,
446
                    DirectiveLocationEnum::FRAGMENT_SPREAD,
447
                    DirectiveLocationEnum::INLINE_FRAGMENT,
448
                ],
449
                'args' => [
450
                    'if ' => [
451
                        'type' => GraphQLNonNull(GraphQLBoolean()),
452
                        'description' => 'Included when true.',
453
                    ],
454
                ],
455
            ]);
456
        });
457
458
        $this->shared('GraphQLSkipDirective', function () {
459
            return GraphQLDirective([
460
                'name' => 'skip',
461
                'description' =>
462
                    'Directs the executor to skip this field or fragment when the `if` ' .
463
                    'argument is true.',
464
                'locations' => [
465
                    DirectiveLocationEnum::FIELD,
466
                    DirectiveLocationEnum::FRAGMENT_SPREAD,
467
                    DirectiveLocationEnum::INLINE_FRAGMENT,
468
                ],
469
                'args' => [
470
                    'if' => [
471
                        'type' => GraphQLNonNull(GraphQLBoolean()),
472
                        'description' => 'Skipped when true.',
473
                    ],
474
                ],
475
            ]);
476
        });
477
478
        $this->shared('GraphQLDeprecatedDirective', function () {
479
            return GraphQLDirective([
480
                'name' => 'deprecated',
481
                'description' => 'Marks an element of a GraphQL schema as no longer supported.',
482
                'locations' => [
483
                    DirectiveLocationEnum::FIELD_DEFINITION,
484
                    DirectiveLocationEnum::ENUM_VALUE,
485
                ],
486
                'args' => [
487
                    'reason' => [
488
                        'type' => GraphQLString(),
489
                        'description' =>
490
                            'Explains why this element was deprecated, usually also including a ' .
491
                            'suggestion for how to access supported similar data. Formatted ' .
492
                            'in [Markdown](https://daringfireball.net/projects/markdown/).',
493
                        'defaultValue' => DEFAULT_DEPRECATION_REASON,
494
                    ],
495
                ]
496
            ]);
497
        });
498
    }
499
500
    /**
501
     * @var array
502
     */
503
    private static $supportedNodeBuilders = [
504
        // Standard
505
        ArgumentBuilder::class,
506
        BooleanBuilder::class,
507
        DirectiveBuilder::class,
508
        DocumentBuilder::class,
509
        EnumBuilder::class,
510
        FieldBuilder::class,
511
        FloatBuilder::class,
512
        FragmentDefinitionBuilder::class,
513
        FragmentSpreadBuilder::class,
514
        InlineFragmentBuilder::class,
515
        IntBuilder::class,
516
        ListBuilder::class,
517
        ListTypeBuilder::class,
518
        NameBuilder::class,
519
        NamedTypeBuilder::class,
520
        NonNullTypeBuilder::class,
521
        NullBuilder::class,
522
        ObjectBuilder::class,
523
        ObjectFieldBuilder::class,
524
        OperationDefinitionBuilder::class,
525
        SelectionSetBuilder::class,
526
        StringBuilder::class,
527
        VariableBuilder::class,
528
        VariableDefinitionBuilder::class,
529
        // Schema Definition Language (SDL)
530
        SchemaDefinitionBuilder::class,
531
        OperationTypeDefinitionBuilder::class,
532
        FieldDefinitionBuilder::class,
533
        ScalarTypeDefinitionBuilder::class,
534
        ObjectTypeDefinitionBuilder::class,
535
        InterfaceTypeDefinitionBuilder::class,
536
        UnionTypeDefinitionBuilder::class,
537
        EnumTypeDefinitionBuilder::class,
538
        EnumValueDefinitionBuilder::class,
539
        InputObjectTypeDefinitionBuilder::class,
540
        InputValueDefinitionBuilder::class,
541
        ScalarTypeExtensionBuilder::class,
542
        ObjectTypeExtensionBuilder::class,
543
        InterfaceTypeExtensionBuilder::class,
544
        EnumTypeExtensionBuilder::class,
545
        UnionTypeExtensionBuilder::class,
546
        InputObjectTypeExtensionBuilder::class,
547
        DirectiveDefinitionBuilder::class,
548
    ];
549
550
    /**
551
     * @var array
552
     */
553
    private static $supportedSourceReaders = [
554
        AmpReader::class,
555
        AtReader::class,
556
        BangReader::class,
557
        BlockStringReader::class,
558
        BraceReader::class,
559
        BracketReader::class,
560
        ColonReader::class,
561
        CommentReader::class,
562
        DollarReader::class,
563
        EqualsReader::class,
564
        NameReader::class,
565
        NumberReader::class,
566
        ParenthesisReader::class,
567
        PipeReader::class,
568
        SpreadReader::class,
569
        StringReader::class,
570
    ];
571
}
572