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

coerceString()   B

Complexity

Conditions 5

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
c 0
b 0
f 0
dl 0
loc 19
rs 8.5454
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\DirectiveBuilder;
8
use Digia\GraphQL\Language\AST\Builder\DirectiveDefinitionBuilder;
9
use Digia\GraphQL\Language\AST\Builder\DocumentBuilder;
10
use Digia\GraphQL\Language\AST\Builder\EnumBuilder;
11
use Digia\GraphQL\Language\AST\Builder\EnumTypeDefinitionBuilder;
12
use Digia\GraphQL\Language\AST\Builder\EnumTypeExtensionBuilder;
13
use Digia\GraphQL\Language\AST\Builder\EnumValueDefinitionBuilder;
14
use Digia\GraphQL\Language\AST\Builder\FieldBuilder;
15
use Digia\GraphQL\Language\AST\Builder\FieldDefinitionBuilder;
16
use Digia\GraphQL\Language\AST\Builder\FloatBuilder;
17
use Digia\GraphQL\Language\AST\Builder\FragmentDefinitionBuilder;
18
use Digia\GraphQL\Language\AST\Builder\FragmentSpreadBuilder;
19
use Digia\GraphQL\Language\AST\Builder\InlineFragmentBuilder;
20
use Digia\GraphQL\Language\AST\Builder\InputObjectTypeDefinitionBuilder;
21
use Digia\GraphQL\Language\AST\Builder\InputObjectTypeExtensionBuilder;
22
use Digia\GraphQL\Language\AST\Builder\InputValueDefinitionBuilder;
23
use Digia\GraphQL\Language\AST\Builder\IntBuilder;
24
use Digia\GraphQL\Language\AST\Builder\InterfaceTypeDefinitionBuilder;
25
use Digia\GraphQL\Language\AST\Builder\InterfaceTypeExtensionBuilder;
26
use Digia\GraphQL\Language\AST\Builder\ListBuilder;
27
use Digia\GraphQL\Language\AST\Builder\ListTypeBuilder;
28
use Digia\GraphQL\Language\AST\Builder\NameBuilder;
29
use Digia\GraphQL\Language\AST\Builder\NamedTypeBuilder;
30
use Digia\GraphQL\Language\AST\Builder\NodeBuilder;
31
use Digia\GraphQL\Language\AST\Builder\NodeBuilderInterface;
32
use Digia\GraphQL\Language\AST\Builder\NonNullTypeBuilder;
33
use Digia\GraphQL\Language\AST\Builder\NullBuilder;
34
use Digia\GraphQL\Language\AST\Builder\ObjectBuilder;
35
use Digia\GraphQL\Language\AST\Builder\ObjectFieldBuilder;
36
use Digia\GraphQL\Language\AST\Builder\ObjectTypeDefinitionBuilder;
37
use Digia\GraphQL\Language\AST\Builder\ObjectTypeExtensionBuilder;
38
use Digia\GraphQL\Language\AST\Builder\OperationDefinitionBuilder;
39
use Digia\GraphQL\Language\AST\Builder\OperationTypeDefinitionBuilder;
40
use Digia\GraphQL\Language\AST\Builder\ScalarTypeDefinitionBuilder;
41
use Digia\GraphQL\Language\AST\Builder\ScalarTypeExtensionBuilder;
42
use Digia\GraphQL\Language\AST\Builder\SchemaDefinitionBuilder;
43
use Digia\GraphQL\Language\AST\Builder\SelectionSetBuilder;
44
use Digia\GraphQL\Language\AST\Builder\StringBuilder;
45
use Digia\GraphQL\Language\AST\Builder\UnionTypeDefinitionBuilder;
46
use Digia\GraphQL\Language\AST\Builder\UnionTypeExtensionBuilder;
47
use Digia\GraphQL\Language\AST\Builder\VariableBuilder;
48
use Digia\GraphQL\Language\AST\Builder\VariableDefinitionBuilder;
49
use Digia\GraphQL\Language\AST\DirectiveLocationEnum;
50
use Digia\GraphQL\Language\AST\Node\BooleanValueNode;
51
use Digia\GraphQL\Language\AST\Node\FloatValueNode;
52
use Digia\GraphQL\Language\AST\Node\IntValueNode;
53
use Digia\GraphQL\Language\AST\Node\NodeInterface;
54
use Digia\GraphQL\Language\AST\Node\StringValueNode;
55
use Digia\GraphQL\Language\AST\NodeKindEnum;
56
use Digia\GraphQL\Language\Lexer;
57
use Digia\GraphQL\Language\LexerInterface;
58
use Digia\GraphQL\Language\Parser;
59
use Digia\GraphQL\Language\ParserInterface;
60
use Digia\GraphQL\Language\Printer;
61
use Digia\GraphQL\Language\PrinterInterface;
62
use Digia\GraphQL\Language\Reader\AmpReader;
63
use Digia\GraphQL\Language\Reader\AtReader;
64
use Digia\GraphQL\Language\Reader\BangReader;
65
use Digia\GraphQL\Language\Reader\BlockStringReader;
66
use Digia\GraphQL\Language\Reader\BraceReader;
67
use Digia\GraphQL\Language\Reader\BracketReader;
68
use Digia\GraphQL\Language\Reader\ColonReader;
69
use Digia\GraphQL\Language\Reader\CommentReader;
70
use Digia\GraphQL\Language\Reader\DollarReader;
71
use Digia\GraphQL\Language\Reader\EqualsReader;
72
use Digia\GraphQL\Language\Reader\NameReader;
73
use Digia\GraphQL\Language\Reader\NumberReader;
74
use Digia\GraphQL\Language\Reader\ParenthesisReader;
75
use Digia\GraphQL\Language\Reader\PipeReader;
76
use Digia\GraphQL\Language\Reader\SpreadReader;
77
use Digia\GraphQL\Language\Reader\StringReader;
78
use Digia\GraphQL\Type\Definition\TypeNameEnum;
79
use League\Container\Container;
80
use League\Container\Definition\DefinitionInterface;
81
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...
82
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...
83
use function Digia\GraphQL\Type\GraphQLBoolean;
84
use function Digia\GraphQL\Type\GraphQLDirective;
85
use function Digia\GraphQL\Type\GraphQLNonNull;
86
use function Digia\GraphQL\Type\GraphQLScalarType;
87
use function Digia\GraphQL\Type\GraphQLString;
88
89
class GraphQLRuntime
90
{
91
92
    private static $instance;
93
94
    /**
95
     * @var Container
96
     */
97
    protected $container;
98
99
    /**
100
     * GraphQL constructor.
101
     */
102
    public function __construct()
103
    {
104
        $this->container = new Container();
105
106
        $this->register();
107
    }
108
109
    /**
110
     * @return GraphQLRuntime
111
     */
112
    public static function getInstance()
113
    {
114
        if (null === self::$instance) {
115
            self::$instance = new static();
116
        }
117
118
        return self::$instance;
119
    }
120
121
    /**
122
     * @param string $id
123
     * @return mixed
124
     */
125
    public function get(string $id)
126
    {
127
        return $this->container->get($id);
128
    }
129
130
    /**
131
     * @param $id
132
     * @param $concrete
133
     */
134
    public function add($id, $concrete)
135
    {
136
        $this->container->add($id, $concrete);
137
    }
138
139
    /**
140
     * @return ParserInterface
141
     */
142
    public function getParser(): ParserInterface
143
    {
144
        return $this->container->get(ParserInterface::class);
145
    }
146
147
    /**
148
     * @return LexerInterface
149
     */
150
    public function getLexer(): LexerInterface
151
    {
152
        return $this->container->get(LexerInterface::class);
153
    }
154
155
    /**
156
     * @return PrinterInterface
157
     */
158
    public function getPrinter(): PrinterInterface
159
    {
160
        return $this->container->get(PrinterInterface::class);
161
    }
162
163
    /**
164
     *
165
     */
166
    protected function register()
167
    {
168
        $this->registerParser();
169
        $this->registerPrinter();
170
        $this->registerScalarTypes();
171
        $this->registerDirectives();
172
    }
173
174
    /**
175
     * Registers the GraphQL parser with the container.
176
     */
177
    protected function registerParser()
178
    {
179
        $this->singleton(NodeBuilderInterface::class, function () {
180
            $builders = [
181
                // Standard
182
                new ArgumentBuilder(),
183
                new BooleanBuilder(),
184
                new DirectiveBuilder(),
185
                new DocumentBuilder(),
186
                new EnumBuilder(),
187
                new FieldBuilder(),
188
                new FloatBuilder(),
189
                new FragmentDefinitionBuilder(),
190
                new FragmentSpreadBuilder(),
191
                new InlineFragmentBuilder(),
192
                new IntBuilder(),
193
                new ListBuilder(),
194
                new ListTypeBuilder(),
195
                new NameBuilder(),
196
                new NamedTypeBuilder(),
197
                new NonNullTypeBuilder(),
198
                new NullBuilder(),
199
                new ObjectBuilder(),
200
                new ObjectFieldBuilder(),
201
                new OperationDefinitionBuilder(),
202
                new SelectionSetBuilder(),
203
                new StringBuilder(),
204
                new VariableBuilder(),
205
                new VariableDefinitionBuilder(),
206
                // Schema Definition Language (SDL)
207
                new SchemaDefinitionBuilder(),
208
                new OperationTypeDefinitionBuilder(),
209
                new FieldDefinitionBuilder(),
210
                new ScalarTypeDefinitionBuilder(),
211
                new ObjectTypeDefinitionBuilder(),
212
                new InterfaceTypeDefinitionBuilder(),
213
                new UnionTypeDefinitionBuilder(),
214
                new EnumTypeDefinitionBuilder(),
215
                new EnumValueDefinitionBuilder(),
216
                new InputObjectTypeDefinitionBuilder(),
217
                new InputValueDefinitionBuilder(),
218
                new ScalarTypeExtensionBuilder(),
219
                new ObjectTypeExtensionBuilder(),
220
                new InterfaceTypeExtensionBuilder(),
221
                new EnumTypeExtensionBuilder(),
222
                new UnionTypeExtensionBuilder(),
223
                new InputObjectTypeExtensionBuilder(),
224
                new DirectiveDefinitionBuilder(),
225
            ];
226
227
            return new NodeBuilder($builders);
228
        });
229
230
        $this
231
            ->singleton(ParserInterface::class, Parser::class)
232
            ->addArgument(NodeBuilderInterface::class);
233
234
        $this->container->add(LexerInterface::class, function () {
235
            $readers = [
236
                new AmpReader(),
237
                new AtReader(),
238
                new BangReader(),
239
                new BlockStringReader(),
240
                new BraceReader(),
241
                new BracketReader(),
242
                new ColonReader(),
243
                new CommentReader(),
244
                new DollarReader(),
245
                new EqualsReader(),
246
                new NameReader(),
247
                new NumberReader(),
248
                new ParenthesisReader(),
249
                new PipeReader(),
250
                new SpreadReader(),
251
                new StringReader(),
252
            ];
253
254
            return new Lexer($readers);
255
        });
256
    }
257
258
    /**
259
     * Registers the GraphQL printer with the container.
260
     */
261
    protected function registerPrinter()
262
    {
263
        $this->singleton(PrinterInterface::class, Printer::class);
264
    }
265
266
    /**
267
     * Registers the GraphQL scalar types with the container.
268
     */
269
    protected function registerScalarTypes()
270
    {
271
        /**
272
         * @param $value
273
         * @return bool
274
         * @throws \TypeError
275
         */
276
        function coerceBoolean($value): bool
277
        {
278
            if (!is_scalar($value)) {
279
                throw new \TypeError(sprintf('Boolean cannot represent a non-scalar value: %s', $value));
280
            }
281
282
            return (bool)$value;
283
        }
284
285
        $this->singleton('GraphQLBoolean', function () {
286
            return GraphQLScalarType([
287
                'name'        => TypeNameEnum::BOOLEAN,
288
                'description' => 'The `Boolean` scalar type represents `true` or `false`.',
289
                'serialize'   => function ($value) {
290
                    return coerceBoolean($value);
291
                },
292
                'parseValue'  => function ($value) {
293
                    return coerceBoolean($value);
294
                },
295
296
                'parseLiteral' => function (NodeInterface $astNode) {
297
                    /** @var BooleanValueNode $astNode */
298
                    return $astNode->getKind() === NodeKindEnum::BOOLEAN ? $astNode->getValue() : null;
299
                },
300
            ]);
301
        });
302
303
        /**
304
         * @param $value
305
         * @return float
306
         * @throws \TypeError
307
         */
308
        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...
309
        {
310
            if ($value === '') {
311
                throw new \TypeError('Float cannot represent non numeric value: (empty string)');
312
            }
313
314
            if (is_numeric($value) || \is_bool($value)) {
315
                return (float)$value;
316
            }
317
318
            throw new \TypeError(sprintf('Float cannot represent non numeric value: %s', $value));
319
        }
320
321
        $this->singleton('GraphQLFloat', function () {
322
            return GraphQLScalarType([
323
                'name'         => TypeNameEnum::FLOAT,
324
                'description'  =>
325
                    'The `Float` scalar type represents signed double-precision fractional ' .
326
                    'values as specified by ' .
327
                    '[IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).',
328
                'serialize'    => function ($value) {
329
                    return coerceFloat($value);
330
                },
331
                'parseValue'   => function ($value) {
332
                    return coerceFloat($value);
333
                },
334
                'parseLiteral' => function (NodeInterface $astNode) {
335
                    /** @var FloatValueNode $astNode */
336
                    return in_array($astNode->getKind(), [NodeKindEnum::FLOAT, NodeKindEnum::INT], true)
337
                        ? $astNode->getValue()
338
                        : null;
339
                },
340
            ]);
341
        });
342
343
        /**
344
         * @param $value
345
         * @return int
346
         * @throws \TypeError
347
         */
348
        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...
349
        {
350
            if ($value === '') {
351
                throw new \TypeError('Int cannot represent non 32-bit signed integer value: (empty string)');
352
            }
353
354
            if (\is_bool($value)) {
355
                $value = (int)$value;
356
            }
357
358
            if (!\is_int($value) || $value > MAX_INT || $value < MIN_INT) {
359
                throw new \TypeError(sprintf('Int cannot represent non 32-bit signed integer value: %s', $value));
360
            }
361
362
            $intValue   = (int)$value;
363
            $floatValue = (float)$value;
364
365
            if ($floatValue != $intValue || floor($floatValue) !== $floatValue) {
366
                throw new \TypeError(sprintf('Int cannot represent non-integer value: %s', $value));
367
            }
368
369
            return $intValue;
370
        }
371
372
        $this->singleton('GraphQLInt', function () {
373
            return GraphQLScalarType([
374
                'name'         => TypeNameEnum::INT,
375
                'description'  =>
376
                    'The `Int` scalar type represents non-fractional signed whole numeric ' .
377
                    'values. Int can represent values between -(2^31) and 2^31 - 1.',
378
                'serialize'    => function ($value) {
379
                    return coerceInt($value);
380
                },
381
                'parseValue'   => function ($value) {
382
                    return coerceInt($value);
383
                },
384
                'parseLiteral' => function (NodeInterface $astNode) {
385
                    /** @var IntValueNode $astNode */
386
                    return $astNode->getKind() === NodeKindEnum::INT ? $astNode->getValue() : null;
387
                },
388
            ]);
389
        });
390
391
        /**
392
         * @param $value
393
         * @return string
394
         * @throws \TypeError
395
         */
396
        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...
397
        {
398
            if ($value === null) {
399
                return 'null';
400
            }
401
402
            if ($value === true) {
403
                return 'true';
404
            }
405
406
            if ($value === false) {
407
                return 'false';
408
            }
409
410
            if (!is_scalar($value)) {
411
                throw new \TypeError('String cannot represent a non-scalar value');
412
            }
413
414
            return (string)$value;
415
        }
416
417
        $this->singleton('GraphQLID', function () {
418
            return GraphQLScalarType([
419
                'name'         => TypeNameEnum::ID,
420
                'description'  =>
421
                    'The `ID` scalar type represents a unique identifier, often used to ' .
422
                    'refetch an object or as key for a cache. The ID type appears in a JSON ' .
423
                    'response as a String; however, it is not intended to be human-readable. ' .
424
                    'When expected as an input type, any string (such as `"4"`) or integer ' .
425
                    '(such as `4`) input value will be accepted as an ID.',
426
                'serialize'    => function ($value) {
427
                    return coerceString($value);
428
                },
429
                'parseValue'   => function ($value) {
430
                    return coerceString($value);
431
                },
432
                'parseLiteral' => function (NodeInterface $astNode) {
433
                    /** @var StringValueNode $astNode */
434
                    return in_array($astNode->getKind(), [NodeKindEnum::STRING, NodeKindEnum::INT], true)
435
                        ? $astNode->getValue()
436
                        : null;
437
                },
438
            ]);
439
        });
440
441
        $this->singleton('GraphQLString', function () {
442
            return GraphQLScalarType([
443
                'name'         => TypeNameEnum::STRING,
444
                'description'  =>
445
                    'The `String` scalar type represents textual data, represented as UTF-8 ' .
446
                    'character sequences. The String type is most often used by GraphQL to ' .
447
                    'represent free-form human-readable text.',
448
                'serialize'    => function ($value) {
449
                    return coerceString($value);
450
                },
451
                'parseValue'   => function ($value) {
452
                    return coerceString($value);
453
                },
454
                'parseLiteral' => function (NodeInterface $astNode) {
455
                    /** @var StringValueNode $astNode */
456
                    return $astNode->getKind() === NodeKindEnum::STRING ? $astNode->getValue() : null;
457
                },
458
            ]);
459
        });
460
    }
461
462
    /**
463
     * Registers the GraphQL directives with the container.
464
     */
465
    protected function registerDirectives()
466
    {
467
        $this->singleton('GraphQLIncludeDirective', function () {
468
            return GraphQLDirective([
469
                'name'        => 'include',
470
                'description' =>
471
                    'Directs the executor to include this field or fragment only when ' .
472
                    'the `if` argument is true.',
473
                'locations'   => [
474
                    DirectiveLocationEnum::FIELD,
475
                    DirectiveLocationEnum::FRAGMENT_SPREAD,
476
                    DirectiveLocationEnum::INLINE_FRAGMENT,
477
                ],
478
                'args'        => [
479
                    'if ' => [
480
                        'type'        => GraphQLNonNull(GraphQLBoolean()),
481
                        'description' => 'Included when true.',
482
                    ],
483
                ],
484
            ]);
485
        });
486
487
        $this->singleton('GraphQLSkipDirective', function () {
488
            return GraphQLDirective([
489
                'name'        => 'skip',
490
                'description' =>
491
                    'Directs the executor to skip this field or fragment when the `if` ' .
492
                    'argument is true.',
493
                'locations'   => [
494
                    DirectiveLocationEnum::FIELD,
495
                    DirectiveLocationEnum::FRAGMENT_SPREAD,
496
                    DirectiveLocationEnum::INLINE_FRAGMENT,
497
                ],
498
                'args'        => [
499
                    'if' => [
500
                        'type'        => GraphQLNonNull(GraphQLBoolean()),
501
                        'description' => 'Skipped when true.',
502
                    ],
503
                ],
504
            ]);
505
        });
506
507
        $this->singleton('GraphQLDeprecatedDirective', function () {
508
            return GraphQLDirective([
509
                'name'        => 'deprecated',
510
                'description' => 'Marks an element of a GraphQL schema as no longer supported.',
511
                'locations'   => [
512
                    DirectiveLocationEnum::FIELD_DEFINITION,
513
                    DirectiveLocationEnum::ENUM_VALUE,
514
                ],
515
                'args'        => [
516
                    'reason' => [
517
                        'type'         => GraphQLString(),
518
                        'description'  =>
519
                            'Explains why this element was deprecated, usually also including a ' .
520
                            'suggestion for how to access supported similar data. Formatted ' .
521
                            'in [Markdown](https://daringfireball.net/projects/markdown/).',
522
                        'defaultValue' => DEFAULT_DEPRECATION_REASON,
523
                    ],
524
                ]
525
            ]);
526
        });
527
    }
528
529
    /**
530
     * @param string $id
531
     * @param mixed  $concrete
532
     * @return DefinitionInterface
533
     */
534
    protected function bind(string $id, $concrete): DefinitionInterface
535
    {
536
        return $this->container->add($id, $concrete);
537
    }
538
539
    /**
540
     * @param string $id
541
     * @param mixed  $concrete
542
     * @return DefinitionInterface
543
     */
544
    protected function singleton(string $id, $concrete): DefinitionInterface
545
    {
546
        return $this->container->add($id, $concrete, true);
547
    }
548
}
549