1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Fubhy\GraphQL\Language; |
4
|
|
|
|
5
|
|
|
use Fubhy\GraphQL\Language\Node\Argument; |
6
|
|
|
use Fubhy\GraphQL\Language\Node\ArrayValue; |
7
|
|
|
use Fubhy\GraphQL\Language\Node\BooleanValue; |
8
|
|
|
use Fubhy\GraphQL\Language\Node\Directive; |
9
|
|
|
use Fubhy\GraphQL\Language\Node\Document; |
10
|
|
|
use Fubhy\GraphQL\Language\Node\EnumValue; |
11
|
|
|
use Fubhy\GraphQL\Language\Node\Field; |
12
|
|
|
use Fubhy\GraphQL\Language\Node\FloatValue; |
13
|
|
|
use Fubhy\GraphQL\Language\Node\FragmentDefinition; |
14
|
|
|
use Fubhy\GraphQL\Language\Node\FragmentSpread; |
15
|
|
|
use Fubhy\GraphQL\Language\Node\InlineFragment; |
16
|
|
|
use Fubhy\GraphQL\Language\Node\IntValue; |
17
|
|
|
use Fubhy\GraphQL\Language\Node\ListType; |
18
|
|
|
use Fubhy\GraphQL\Language\Node\Name; |
19
|
|
|
use Fubhy\GraphQL\Language\Node\NamedType; |
20
|
|
|
use Fubhy\GraphQL\Language\Node\NonNullType; |
21
|
|
|
use Fubhy\GraphQL\Language\Node\ObjectField; |
22
|
|
|
use Fubhy\GraphQL\Language\Node\ObjectValue; |
23
|
|
|
use Fubhy\GraphQL\Language\Node\OperationDefinition; |
24
|
|
|
use Fubhy\GraphQL\Language\Node\SelectionSet; |
25
|
|
|
use Fubhy\GraphQL\Language\Node\StringValue; |
26
|
|
|
use Fubhy\GraphQL\Language\Node\Variable; |
27
|
|
|
use Fubhy\GraphQL\Language\Node\VariableDefinition; |
28
|
|
|
|
29
|
|
|
class Parser |
30
|
|
|
{ |
31
|
|
|
/** |
32
|
|
|
* @var array |
33
|
|
|
*/ |
34
|
|
|
protected $options; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var \Fubhy\GraphQL\Language\Source |
38
|
|
|
*/ |
39
|
|
|
protected $source; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var \Fubhy\GraphQL\Language\Lexer |
43
|
|
|
*/ |
44
|
|
|
protected $lexer; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @var \Fubhy\GraphQL\Language\Token |
48
|
|
|
*/ |
49
|
|
|
protected $token; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @var int |
53
|
|
|
*/ |
54
|
|
|
protected $cursor; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Constructor. |
58
|
|
|
* |
59
|
|
|
* Returns the parser object that is used to store state throughout the |
60
|
|
|
* process of parsing. |
61
|
|
|
* |
62
|
|
|
* @param array $options |
63
|
|
|
*/ |
64
|
309 |
|
public function __construct(array $options = []) |
65
|
|
|
{ |
66
|
309 |
|
$this->options = $options; |
67
|
309 |
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @param \Fubhy\GraphQL\Language\Source $source |
71
|
|
|
* |
72
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Document |
73
|
|
|
*/ |
74
|
309 |
|
public function parse(Source $source) |
75
|
|
|
{ |
76
|
309 |
|
$this->source = $source; |
77
|
309 |
|
$this->lexer = new Lexer($source); |
78
|
309 |
|
$this->token = $this->lexer->readToken(0); |
79
|
309 |
|
$this->cursor = 0; |
80
|
|
|
|
81
|
309 |
|
return $this->parseDocument(); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Returns a location object, used to identify the place in the source that |
86
|
|
|
* created a given parsed object. |
87
|
|
|
* |
88
|
|
|
* @param int $start |
89
|
|
|
* |
90
|
|
|
* @return \Fubhy\GraphQL\Language\Location|null |
91
|
|
|
*/ |
92
|
309 |
|
protected function location($start) |
93
|
|
|
{ |
94
|
309 |
|
if (!empty($this->options['noLocation'])) { |
95
|
|
|
return NULL; |
96
|
|
|
} |
97
|
|
|
|
98
|
309 |
|
if (!empty($this->options['noSource'])) { |
99
|
|
|
return new Location($start, $this->cursor); |
100
|
|
|
} |
101
|
|
|
|
102
|
309 |
|
return new Location($start, $this->cursor, $this->source); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Moves the internal parser object to the next lexed token. |
108
|
|
|
*/ |
109
|
309 |
|
protected function advance() |
110
|
|
|
{ |
111
|
309 |
|
$this->cursor = $this->token->getEnd(); |
112
|
309 |
|
return $this->token = $this->lexer->readToken($this->cursor); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Determines if the next token is of a given kind |
117
|
|
|
* |
118
|
|
|
* @param int $type |
119
|
|
|
* |
120
|
|
|
* @return bool |
121
|
|
|
*/ |
122
|
309 |
|
protected function peek($type) |
123
|
|
|
{ |
124
|
309 |
|
return $this->token->getType() === $type; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* If the next token is of the given kind, return true after advancing the |
129
|
|
|
* parser. Otherwise, do not change the parser state and return false. |
130
|
|
|
* |
131
|
|
|
* @param int $type |
132
|
|
|
* |
133
|
|
|
* @return bool |
134
|
|
|
*/ |
135
|
309 |
|
protected function skip($type) |
136
|
|
|
{ |
137
|
309 |
|
if ($match = ($this->token->getType() === $type)) { |
138
|
309 |
|
$this->advance(); |
139
|
309 |
|
} |
140
|
|
|
|
141
|
309 |
|
return $match; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* If the next token is of the given kind, return that token after advancing |
146
|
|
|
* the parser. Otherwise, do not change the parser state and return false. |
147
|
|
|
* |
148
|
|
|
* @param int $type |
149
|
|
|
* |
150
|
|
|
* @return \Fubhy\GraphQL\Language\Token |
151
|
|
|
* |
152
|
|
|
* @throws \Exception |
153
|
|
|
*/ |
154
|
309 |
|
protected function expect($type) |
155
|
|
|
{ |
156
|
309 |
|
if ($this->token->getType() !== $type) { |
157
|
|
|
throw new \Exception(sprintf('Expected %s, found %s', Token::typeToString($type), (string) $this->token)); |
158
|
|
|
} |
159
|
|
|
|
160
|
309 |
|
$token = $this->token; |
161
|
309 |
|
$this->advance(); |
162
|
309 |
|
return $token; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* If the next token is a keyword with the given value, return that token |
167
|
|
|
* after advancing the parser. Otherwise, do not change the parser state and |
168
|
|
|
* return false. |
169
|
|
|
* |
170
|
|
|
* @param string $value |
171
|
|
|
* |
172
|
|
|
* @return \Fubhy\GraphQL\Language\Token |
173
|
|
|
* |
174
|
|
|
* @throws \Exception |
175
|
|
|
*/ |
176
|
36 |
|
protected function expectKeyword($value) |
177
|
|
|
{ |
178
|
36 |
|
if ($this->token->getType() !== Token::NAME_TYPE || $this->token->getValue() !== $value) { |
179
|
|
|
throw new \Exception(sprintf('Expected %s, found %s', $value, $this->token->getDescription())); |
180
|
|
|
} |
181
|
|
|
|
182
|
36 |
|
return $this->advance(); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Helper protected function for creating an error when an unexpected lexed token is |
187
|
|
|
* encountered. |
188
|
|
|
* |
189
|
|
|
* @param \Fubhy\GraphQL\Language\Token|null $atToken |
190
|
|
|
* |
191
|
|
|
* @return \Exception |
192
|
|
|
*/ |
193
|
|
|
protected function unexpected(Token $atToken = NULL) |
194
|
|
|
{ |
195
|
|
|
$token = $atToken ?: $this->token; |
196
|
|
|
|
197
|
|
|
return new \Exception(sprintf('Unexpected %s', $token->getDescription())); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Returns a possibly empty list of parse nodes, determined by the parseFn. |
202
|
|
|
* |
203
|
|
|
* This list begins with a lex token of openKind and ends with a lex token |
204
|
|
|
* of closeKind. Advances the parser to the next lex token after the closing |
205
|
|
|
* token. |
206
|
|
|
* |
207
|
|
|
* @param int $openKind |
208
|
|
|
* @param callable $parseFn |
209
|
|
|
* @param int $closeKind |
210
|
|
|
* |
211
|
|
|
* @return array |
212
|
|
|
*/ |
213
|
9 |
View Code Duplication |
protected function any($openKind, $parseFn, $closeKind) |
|
|
|
|
214
|
|
|
{ |
215
|
9 |
|
$this->expect($openKind); |
216
|
9 |
|
$nodes = []; |
217
|
|
|
|
218
|
9 |
|
while (!$this->skip($closeKind)) { |
219
|
9 |
|
array_push($nodes, $parseFn($this)); |
220
|
9 |
|
} |
221
|
|
|
|
222
|
9 |
|
return $nodes; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Returns a non-empty list of parse nodes, determined by the parseFn. |
227
|
|
|
* |
228
|
|
|
* This list begins with a lex token of openKind and ends with a lex token |
229
|
|
|
* of closeKind. Advances the parser to the next lex token after the closing |
230
|
|
|
* token. |
231
|
|
|
* |
232
|
|
|
* @param int $openKind |
233
|
|
|
* @param callable $parseFn |
234
|
|
|
* @param int $closeKind |
235
|
|
|
* |
236
|
|
|
* @return array |
237
|
|
|
*/ |
238
|
309 |
View Code Duplication |
protected function many($openKind, $parseFn, $closeKind) |
|
|
|
|
239
|
|
|
{ |
240
|
309 |
|
$this->expect($openKind); |
241
|
309 |
|
$nodes = [$parseFn($this)]; |
242
|
|
|
|
243
|
309 |
|
while (!$this->skip($closeKind)) { |
244
|
123 |
|
array_push($nodes, $parseFn($this)); |
245
|
123 |
|
} |
246
|
|
|
|
247
|
309 |
|
return $nodes; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Converts a name lex token into a name parse node. |
252
|
|
|
* |
253
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Name |
254
|
|
|
*/ |
255
|
309 |
|
protected function parseName() |
256
|
|
|
{ |
257
|
309 |
|
$start = $this->token->getStart(); |
258
|
309 |
|
$token = $this->expect(Token::NAME_TYPE); |
259
|
|
|
|
260
|
309 |
|
return new Name($token->getValue(), $this->location($start)); |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* Converts a fragment name lex token into a name parse node. |
265
|
|
|
* |
266
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Name |
267
|
|
|
* |
268
|
|
|
* @throws \Exception |
269
|
|
|
*/ |
270
|
33 |
|
protected function parseFragmentName() |
271
|
|
|
{ |
272
|
33 |
|
if ($this->token->getValue() === 'on') { |
273
|
|
|
throw $this->unexpected(); |
274
|
|
|
} |
275
|
33 |
|
return $this->parseName(); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Document |
280
|
|
|
* |
281
|
|
|
* @throws \Exception |
282
|
|
|
*/ |
283
|
309 |
|
protected function parseDocument() |
284
|
|
|
{ |
285
|
309 |
|
$start = $this->token->getStart(); |
286
|
309 |
|
$definitions = []; |
287
|
|
|
|
288
|
|
|
do { |
289
|
309 |
|
if ($this->peek(Token::BRACE_L_TYPE)) { |
290
|
87 |
|
$definitions[] = $this->parseOperationDefinition(); |
291
|
309 |
|
} else if ($this->peek(Token::NAME_TYPE)) { |
292
|
237 |
|
$value = $this->token->getValue(); |
293
|
237 |
|
if ($value === 'query' || $value === 'mutation') { |
294
|
225 |
|
$definitions[] = $this->parseOperationDefinition(); |
295
|
237 |
|
} else if ($value === 'fragment') { |
296
|
36 |
|
$definitions[] = $this->parseFragmentDefinition(); |
297
|
36 |
|
} else { |
298
|
|
|
throw $this->unexpected(); |
299
|
|
|
} |
300
|
237 |
|
} else { |
301
|
|
|
throw $this->unexpected(); |
302
|
|
|
} |
303
|
309 |
|
} while (!$this->skip(Token::EOF_TYPE)); |
304
|
|
|
|
305
|
309 |
|
return new Document($definitions, $this->location($start)); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* @return \Fubhy\GraphQL\Language\Node\OperationDefinition |
310
|
|
|
*/ |
311
|
309 |
|
protected function parseOperationDefinition() |
312
|
|
|
{ |
313
|
309 |
|
$start = $this->token->getStart(); |
314
|
|
|
|
315
|
309 |
|
if ($this->peek(Token::BRACE_L_TYPE)) { |
316
|
87 |
|
return new OperationDefinition('query', NULL, [], [], |
317
|
87 |
|
$this->parseSelectionSet(), |
318
|
87 |
|
$this->location($start) |
319
|
87 |
|
); |
320
|
|
|
} |
321
|
|
|
|
322
|
225 |
|
$operationToken = $this->expect(Token::NAME_TYPE); |
323
|
225 |
|
$operation = $operationToken->getValue(); |
324
|
|
|
|
325
|
225 |
|
return new OperationDefinition($operation, |
326
|
225 |
|
$this->parseName(), |
327
|
225 |
|
$this->parseVariableDefinitions(), |
328
|
225 |
|
$this->parseDirectives(), |
329
|
225 |
|
$this->parseSelectionSet(), |
330
|
225 |
|
$this->location($start) |
331
|
225 |
|
); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* @return \Fubhy\GraphQL\Language\Node\VariableDefinition[] |
336
|
|
|
*/ |
337
|
225 |
|
protected function parseVariableDefinitions() |
338
|
|
|
{ |
339
|
225 |
|
return $this->peek(Token::PAREN_L_TYPE) ? $this->many(Token::PAREN_L_TYPE, [$this, 'parseVariableDefinition'], Token::PAREN_R_TYPE) : []; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* @return \Fubhy\GraphQL\Language\Node\VariableDefinition |
344
|
|
|
*/ |
345
|
72 |
|
protected function parseVariableDefinition() |
346
|
|
|
{ |
347
|
72 |
|
$start = $this->token->getStart(); |
348
|
72 |
|
$variable = $this->parseVariable(); |
349
|
72 |
|
$this->expect(Token::COLON_TYPE); |
350
|
|
|
|
351
|
72 |
|
return new VariableDefinition( |
352
|
72 |
|
$variable, |
353
|
72 |
|
$this->parseType(), |
354
|
72 |
|
$this->skip(Token::EQUALS_TYPE) ? $this->parseValue(TRUE) : NULL, |
355
|
72 |
|
$this->location($start) |
356
|
72 |
|
); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
/** |
360
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Variable |
361
|
|
|
*/ |
362
|
78 |
|
protected function parseVariable() { |
363
|
78 |
|
$start = $this->token->getStart(); |
364
|
78 |
|
$this->expect(Token::DOLLAR_TYPE); |
365
|
|
|
|
366
|
78 |
|
return new Variable($this->parseName(), $this->location($start)); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* @return \Fubhy\GraphQL\Language\Node\SelectionSet |
371
|
|
|
*/ |
372
|
309 |
|
protected function parseSelectionSet() |
373
|
|
|
{ |
374
|
309 |
|
$start = $this->token->getStart(); |
375
|
|
|
|
376
|
309 |
|
return new SelectionSet( |
377
|
309 |
|
$this->many(Token::BRACE_L_TYPE, [$this, 'parseSelection'], Token::BRACE_R_TYPE), |
378
|
309 |
|
$this->location($start) |
379
|
309 |
|
); |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* @return \Fubhy\GraphQL\Language\Node\SelectionInterface |
384
|
|
|
*/ |
385
|
309 |
|
protected function parseSelection() |
386
|
|
|
{ |
387
|
309 |
|
return $this->peek(Token::SPREAD_TYPE) ? $this->parseFragment() : $this->parseField(); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Field |
392
|
|
|
*/ |
393
|
309 |
|
protected function parseField() |
394
|
|
|
{ |
395
|
309 |
|
$start = $this->token->getStart(); |
396
|
309 |
|
$name = $this->parseName(); |
397
|
309 |
|
$alias = NULL; |
398
|
|
|
|
399
|
309 |
|
if ($this->skip(Token::COLON_TYPE)) { |
400
|
36 |
|
$alias = $name; |
401
|
36 |
|
$name = $this->parseName(); |
402
|
36 |
|
} |
403
|
|
|
|
404
|
309 |
|
return new Field($name, $alias, |
405
|
309 |
|
$this->parseArguments(), |
406
|
309 |
|
$this->parseDirectives(), |
407
|
309 |
|
$this->peek(Token::BRACE_L_TYPE) ? $this->parseSelectionSet() : NULL, |
408
|
309 |
|
$this->location($start) |
409
|
309 |
|
); |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Argument[] |
414
|
|
|
*/ |
415
|
309 |
|
protected function parseArguments() |
416
|
|
|
{ |
417
|
309 |
|
return $this->peek(Token::PAREN_L_TYPE) ? $this->many(Token::PAREN_L_TYPE, [$this, 'parseArgument'], Token::PAREN_R_TYPE) : []; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Argument |
422
|
|
|
*/ |
423
|
168 |
|
protected function parseArgument() |
424
|
|
|
{ |
425
|
168 |
|
$start = $this->token->getStart(); |
426
|
168 |
|
$name = $this->parseName(); |
427
|
|
|
|
428
|
168 |
|
$this->expect(Token::COLON_TYPE); |
429
|
168 |
|
$value = $this->parseValue(FALSE); |
430
|
|
|
|
431
|
168 |
|
return new Argument($name, $value, $this->location($start)); |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* Corresponds to both FragmentSpread and InlineFragment in the spec. |
436
|
|
|
* |
437
|
|
|
* @return \Fubhy\GraphQL\Language\Node\FragmentSpread|\Fubhy\GraphQL\Language\Node\InlineFragment |
438
|
|
|
*/ |
439
|
42 |
|
protected function parseFragment() |
440
|
|
|
{ |
441
|
42 |
|
$start = $this->token->getStart(); |
442
|
42 |
|
$this->expect(Token::SPREAD_TYPE); |
443
|
|
|
|
444
|
42 |
|
if ($this->token->getValue() === 'on') { |
445
|
18 |
|
$this->advance(); |
446
|
|
|
|
447
|
18 |
|
return new InlineFragment( |
448
|
18 |
|
$this->parseNamedType(), |
449
|
18 |
|
$this->parseDirectives(), |
450
|
18 |
|
$this->parseSelectionSet(), |
451
|
18 |
|
$this->location($start) |
452
|
18 |
|
); |
453
|
|
|
} |
454
|
|
|
|
455
|
33 |
|
return new FragmentSpread( |
456
|
33 |
|
$this->parseFragmentName(), |
457
|
33 |
|
$this->parseDirectives(), |
458
|
33 |
|
$this->location($start) |
459
|
33 |
|
); |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
/** |
463
|
|
|
* @return \Fubhy\GraphQL\Language\Node\FragmentDefinition |
464
|
|
|
*/ |
465
|
36 |
|
protected function parseFragmentDefinition() |
466
|
|
|
{ |
467
|
36 |
|
$start = $this->token->getStart(); |
468
|
36 |
|
$this->expectKeyword('fragment'); |
469
|
36 |
|
$name = $this->parseName(); |
470
|
36 |
|
$this->expectKeyword('on'); |
471
|
36 |
|
$typeCondition = $this->parseNamedType(); |
472
|
|
|
|
473
|
36 |
|
return new FragmentDefinition( |
474
|
36 |
|
$name, |
475
|
36 |
|
$typeCondition, |
476
|
36 |
|
$this->parseDirectives(), |
477
|
36 |
|
$this->parseSelectionSet(), |
478
|
36 |
|
$this->location($start) |
479
|
36 |
|
); |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
/** |
483
|
|
|
* @return \Fubhy\GraphQL\Language\Node\ValueInterface |
484
|
|
|
*/ |
485
|
9 |
|
protected function parseVariableValue() |
486
|
|
|
{ |
487
|
9 |
|
return $this->parseValue(FALSE); |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
/** |
491
|
|
|
* @return \Fubhy\GraphQL\Language\Node\ValueInterface |
492
|
|
|
*/ |
493
|
|
|
protected function parseConstValue() |
494
|
|
|
{ |
495
|
|
|
return $this->parseValue(TRUE); |
496
|
|
|
} |
497
|
|
|
|
498
|
|
|
/** |
499
|
|
|
* @param bool $isConst |
500
|
|
|
* |
501
|
|
|
* @return \Fubhy\GraphQL\Language\Node\ValueInterface |
502
|
|
|
* |
503
|
|
|
* @throws \Exception |
504
|
|
|
*/ |
505
|
168 |
|
protected function parseValue($isConst) |
506
|
|
|
{ |
507
|
168 |
|
$start = $this->token->getStart(); |
508
|
168 |
|
$token = $this->token; |
509
|
|
|
|
510
|
168 |
|
switch ($this->token->getType()) { |
511
|
168 |
|
case Token::BRACKET_L_TYPE: |
512
|
9 |
|
return $this->parseArray($isConst); |
513
|
168 |
|
case Token::BRACE_L_TYPE: |
514
|
9 |
|
return $this->parseObject($isConst); |
515
|
168 |
|
case Token::INT_TYPE: |
516
|
12 |
|
$this->advance(); |
517
|
12 |
|
return new IntValue($token->getValue(), $this->location($start)); |
518
|
165 |
|
case Token::FLOAT_TYPE: |
519
|
|
|
$this->advance(); |
520
|
|
|
return new FloatValue($token->getValue(), $this->location($start)); |
521
|
165 |
|
case Token::STRING_TYPE: |
522
|
75 |
|
$this->advance(); |
523
|
75 |
|
return new StringValue($token->getValue(), $this->location($start)); |
524
|
105 |
|
case Token::NAME_TYPE: |
525
|
30 |
|
$this->advance(); |
526
|
30 |
|
switch ($value = $token->getValue()) { |
527
|
30 |
|
case 'true': |
528
|
30 |
|
case 'false': |
529
|
24 |
|
return new BooleanValue($value === 'true', $this->location($start)); |
530
|
6 |
|
} |
531
|
6 |
|
return new EnumValue($value, $this->location($start)); |
532
|
78 |
|
case Token::DOLLAR_TYPE: |
533
|
78 |
|
if (!$isConst) { |
534
|
78 |
|
return $this->parseVariable(); |
535
|
|
|
} |
536
|
|
|
break; |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
throw $this->unexpected(); |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* @param bool $isConst |
544
|
|
|
* |
545
|
|
|
* @return \Fubhy\GraphQL\Language\Node\ArrayValue |
546
|
|
|
*/ |
547
|
9 |
|
protected function parseArray($isConst) |
548
|
|
|
{ |
549
|
9 |
|
$start = $this->token->getStart(); |
550
|
9 |
|
$item = $isConst ? 'parseConstValue' : 'parseVariableValue'; |
551
|
|
|
|
552
|
9 |
|
return new ArrayValue( |
553
|
9 |
|
$this->any(Token::BRACKET_L_TYPE, [$this, $item], Token::BRACKET_R_TYPE), |
554
|
9 |
|
$this->location($start) |
555
|
9 |
|
); |
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
/** |
559
|
|
|
* @param bool $isConst |
560
|
|
|
* |
561
|
|
|
* @return \Fubhy\GraphQL\Language\Node\ObjectValue |
562
|
|
|
*/ |
563
|
9 |
|
protected function parseObject($isConst) |
564
|
|
|
{ |
565
|
9 |
|
$start = $this->token->getStart(); |
566
|
9 |
|
$this->expect(Token::BRACE_L_TYPE); |
567
|
|
|
|
568
|
9 |
|
$fieldNames = []; |
569
|
9 |
|
$fields = []; |
570
|
9 |
|
while (!$this->skip(Token::BRACE_R_TYPE)) { |
571
|
9 |
|
array_push($fields, $this->parseObjectField($isConst, $fieldNames)); |
572
|
9 |
|
} |
573
|
|
|
|
574
|
9 |
|
return new ObjectValue($fields, $this->location($start)); |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
/** |
578
|
|
|
* @param bool $isConst |
579
|
|
|
* @param array $fieldNames |
580
|
|
|
* |
581
|
|
|
* @return \Fubhy\GraphQL\Language\Node\ObjectField |
582
|
|
|
* |
583
|
|
|
* @throws \Exception |
584
|
|
|
*/ |
585
|
9 |
|
protected function parseObjectField($isConst, &$fieldNames) |
586
|
|
|
{ |
587
|
9 |
|
$start = $this->token->getStart(); |
588
|
9 |
|
$name = $this->parseName(); |
589
|
9 |
|
$value = $name->get('value'); |
590
|
|
|
|
591
|
9 |
|
if (array_key_exists($value, $fieldNames)) { |
592
|
|
|
throw new \Exception(sprintf('Duplicate input object field %s.', $value)); |
593
|
|
|
} |
594
|
|
|
|
595
|
9 |
|
$fieldNames[$value] = TRUE; |
596
|
9 |
|
$this->expect(Token::COLON_TYPE); |
597
|
|
|
|
598
|
9 |
|
return new ObjectField($name, $this->parseValue($isConst), $this->location($start)); |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Directive[] |
603
|
|
|
*/ |
604
|
309 |
|
protected function parseDirectives() |
605
|
|
|
{ |
606
|
309 |
|
$directives = []; |
607
|
309 |
|
while ($this->peek(Token::AT_TYPE)) { |
608
|
15 |
|
array_push($directives, $this->parseDirective()); |
609
|
15 |
|
} |
610
|
|
|
|
611
|
309 |
|
return $directives; |
612
|
|
|
} |
613
|
|
|
|
614
|
|
|
/** |
615
|
|
|
* @return \Fubhy\GraphQL\Language\Node\Directive |
616
|
|
|
* |
617
|
|
|
* @throws \Exception |
618
|
|
|
*/ |
619
|
15 |
|
protected function parseDirective() |
620
|
|
|
{ |
621
|
15 |
|
$start = $this->token->getStart(); |
622
|
15 |
|
$this->expect(Token::AT_TYPE); |
623
|
|
|
|
624
|
15 |
|
return new Directive( |
625
|
15 |
|
$this->parseName(), |
626
|
15 |
|
$this->parseArguments(), |
627
|
15 |
|
$this->location($start) |
628
|
15 |
|
); |
629
|
|
|
} |
630
|
|
|
|
631
|
|
|
/** |
632
|
|
|
* Handles the type: TypeName, ListType, and NonNullType parsing rules. |
633
|
|
|
* |
634
|
|
|
* @return \Fubhy\GraphQL\Language\Node\TypeInterface |
635
|
|
|
* |
636
|
|
|
* @throws \Exception |
637
|
|
|
*/ |
638
|
72 |
|
protected function parseType() |
639
|
|
|
{ |
640
|
72 |
|
$start = $this->token->getStart(); |
641
|
|
|
|
642
|
72 |
|
if ($this->skip(Token::BRACKET_L_TYPE)) { |
643
|
36 |
|
$type = $this->parseType(); |
644
|
36 |
|
$this->expect(Token::BRACKET_R_TYPE); |
645
|
36 |
|
$type = new ListType($type, $this->location($start)); |
646
|
36 |
|
} else { |
647
|
72 |
|
$type = $this->parseNamedType(); |
648
|
|
|
} |
649
|
|
|
|
650
|
72 |
|
if ($this->skip(Token::BANG_TYPE)) { |
651
|
45 |
|
return new NonNullType($type, $this->location($start)); |
652
|
|
|
} |
653
|
|
|
|
654
|
45 |
|
return $type; |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
/** |
658
|
|
|
* @return \Fubhy\GraphQL\Language\Node\NamedType |
659
|
|
|
*/ |
660
|
108 |
|
protected function parseNamedType() |
661
|
|
|
{ |
662
|
108 |
|
$start = $this->token->getStart(); |
663
|
|
|
|
664
|
108 |
|
return new NamedType($this->parseName(), $this->location($start)); |
665
|
|
|
} |
666
|
|
|
} |
667
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.