Completed
Push — master ( 11b38d...23fbf3 )
by Alexandr
03:07
created

Parser::parseListValue()   C

Complexity

Conditions 10
Paths 10

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 10.0244

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 15
cts 16
cp 0.9375
rs 5.6534
c 0
b 0
f 0
cc 10
eloc 16
nc 10
nop 0
crap 10.0244

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
* This file is a part of graphql-youshido project.
4
*
5
* @author Portey Vasil <[email protected]>
6
* created: 11/23/15 1:22 AM
7
*/
8
9
namespace Youshido\GraphQL\Parser;
10
11
12
use Youshido\GraphQL\Exception\Parser\SyntaxErrorException;
13
use Youshido\GraphQL\Parser\Ast\Argument;
14
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputList;
15
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputObject;
16
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal;
17
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Variable;
18
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference;
19
use Youshido\GraphQL\Parser\Ast\Field;
20
use Youshido\GraphQL\Parser\Ast\Fragment;
21
use Youshido\GraphQL\Parser\Ast\FragmentReference;
22
use Youshido\GraphQL\Parser\Ast\Mutation;
23
use Youshido\GraphQL\Parser\Ast\Query;
24
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;
25
26
class Parser extends Tokenizer
27
{
28
29
    /** @var array */
30
    private $data = [];
31
32 97
    public function parse($source = null)
33
    {
34 97
        $this->init($source);
35
36 97
        while (!$this->end()) {
37 96
            $tokenType = $this->peek()->getType();
38
39
            switch ($tokenType) {
40 96
                case Token::TYPE_LBRACE:
41 45
                case Token::TYPE_QUERY:
42 79
                    foreach ($this->parseBody() as $query) {
43 72
                        $this->data['queries'][] = $query;
44
                    }
45
46 75
                    break;
47
48 25
                case Token::TYPE_MUTATION:
49 16
                    foreach ($this->parseBody(Token::TYPE_MUTATION) as $query) {
50 11
                        $this->data['mutations'][] = $query;
51
                    }
52
53 11
                    break;
54
55 9
                case Token::TYPE_FRAGMENT:
56 8
                    $this->data['fragments'][] = $this->parseFragment();
57
58 8
                    break;
59
60
                default:
61 1
                    throw new SyntaxErrorException('Incorrect request syntax', $this->getLocation());
62
            }
63
        }
64
65 87
        return $this->data;
66
    }
67
68 97
    private function init($source = null)
69
    {
70 97
        $this->initTokenizer($source);
71
72 97
        $this->data = [
73
            'queries'            => [],
74
            'mutations'          => [],
75
            'fragments'          => [],
76
            'fragmentReferences' => [],
77
            'variables'          => [],
78
            'variableReferences' => []
79
        ];
80 97
    }
81
82 95
    protected function parseBody($token = Token::TYPE_QUERY, $highLevel = true)
83
    {
84 95
        $fields = [];
85
86 95
        if ($highLevel && $this->peek()->getType() === $token) {
87 41
            $this->lex();
88 41
            $this->eat(Token::TYPE_IDENTIFIER);
89
90 41
            if ($this->match(Token::TYPE_LPAREN)) {
91 12
                $this->parseVariables();
92
            }
93
        }
94
95 95
        $this->lex();
96
97 95
        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
98 92
            if ($this->match(Token::TYPE_FRAGMENT_REFERENCE)) {
99 10
                $this->lex();
100
101 10
                if ($this->eat(Token::TYPE_ON)) {
102 3
                    $fields[] = $this->parseBodyItem(Token::TYPE_TYPED_FRAGMENT, $highLevel);
103
                } else {
104 10
                    $fields[] = $this->parseFragmentReference();
105
                }
106
            } else {
107 92
                $fields[] = $this->parseBodyItem($token, $highLevel);
108
            }
109
        }
110
111 87
        $this->expect(Token::TYPE_RBRACE);
112
113 87
        return $fields;
114
    }
115
116 12
    protected function parseVariables()
117
    {
118 12
        $this->eat(Token::TYPE_LPAREN);
119
120 12
        while (!$this->match(Token::TYPE_RPAREN) && !$this->end()) {
121 12
            $variableToken = $this->eat(Token::TYPE_VARIABLE);
122 12
            $nameToken     = $this->eatIdentifierToken();
123 12
            $this->eat(Token::TYPE_COLON);
124
125 12
            $isArray              = false;
126 12
            $arrayElementNullable = true;
127
128 12
            if ($this->match(Token::TYPE_LSQUARE_BRACE)) {
129 2
                $isArray = true;
130
131 2
                $this->eat(Token::TYPE_LSQUARE_BRACE);
132 2
                $type = $this->eatIdentifierToken()->getData();
133
134 2
                if ($this->match(Token::TYPE_REQUIRED)) {
135 2
                    $arrayElementNullable = false;
136 2
                    $this->eat(Token::TYPE_REQUIRED);
137
                }
138
139 2
                $this->eat(Token::TYPE_RSQUARE_BRACE);
140
            } else {
141 11
                $type = $this->eatIdentifierToken()->getData();
142
            }
143
144 12
            $required = false;
145 12
            if ($this->match(Token::TYPE_REQUIRED)) {
146 2
                $required = true;
147 2
                $this->eat(Token::TYPE_REQUIRED);
148
            }
149
150 12
             $variable = new Variable(
151 12
                $nameToken->getData(),
152
                $type,
153
                $required,
154
                $isArray,
155 12
                new Location($variableToken->getLine(), $variableToken->getColumn()),
156 12
                $arrayElementNullable
157
            );
158
159 12
            if ($this->match(Token::TYPE_EQUAL)) {
160 1
                $this->eat(Token::TYPE_EQUAL);
161 1
                $variable->setDefaultValue($this->parseValue());
162
            }
163
164 12
            $this->data['variables'][] = $variable;
165
        }
166
167 12
        $this->expect(Token::TYPE_RPAREN);
168 12
    }
169
170 92
    protected function expectMulti($types)
171
    {
172 92
        if ($this->matchMulti($types)) {
173 92
            return $this->lex();
174
        }
175
176 2
        throw $this->createUnexpectedException($this->peek());
177
    }
178
179 11
    protected function parseVariableReference()
180
    {
181 11
        $startToken = $this->expectMulti([Token::TYPE_VARIABLE]);
182
183 11
        if ($this->match(Token::TYPE_NUMBER) || $this->match(Token::TYPE_IDENTIFIER) || $this->match(Token::TYPE_QUERY)) {
184 11
            $name = $this->lex()->getData();
185
186 11
            $variable = $this->findVariable($name);
187 11
            if ($variable) {
188 11
                $variable->setUsed(true);
189
            }
190
191 11
            $variableReference = new VariableReference($name, $variable, new Location($startToken->getLine(), $startToken->getColumn()));
192
193 11
            $this->data['variableReferences'][] = $variableReference;
194
195 11
            return $variableReference;
196
        }
197
198
        throw $this->createUnexpectedException($this->peek());
199
    }
200
201 11
    protected function findVariable($name)
202
    {
203 11
        foreach ($this->data['variables'] as $variable) {
204
            /** @var $variable Variable */
205 11
            if ($variable->getName() == $name) {
206 11
                return $variable;
207
            }
208
        }
209
210
        return null;
211
    }
212
213 8
    protected function parseFragmentReference()
214
    {
215 8
        $nameToken         = $this->eatIdentifierToken();
216 8
        $fragmentReference = new FragmentReference($nameToken->getData(), new Location($nameToken->getLine(), $nameToken->getColumn()));
217
218 8
        $this->data['fragmentReferences'][] = $fragmentReference;
219
220 8
        return $fragmentReference;
221
    }
222
223 92
    protected function eatIdentifierToken()
224
    {
225 92
        return $this->expectMulti([
226 92
            Token::TYPE_IDENTIFIER,
227 92
            Token::TYPE_MUTATION,
228 92
            Token::TYPE_QUERY,
229 92
            Token::TYPE_FRAGMENT,
230
        ]);
231
    }
232
233 92
    protected function parseBodyItem($type = Token::TYPE_QUERY, $highLevel = true)
234
    {
235 92
        $nameToken = $this->eatIdentifierToken();
236 92
        $alias     = null;
237
238 92
        if ($this->eat(Token::TYPE_COLON)) {
239 17
            $alias     = $nameToken->getData();
240 17
            $nameToken = $this->eatIdentifierToken();
241
        }
242
243 92
        $bodyLocation = new Location($nameToken->getLine(), $nameToken->getColumn());
244 92
        $arguments    = $this->match(Token::TYPE_LPAREN) ? $this->parseArgumentList() : [];
245
246 85
        if ($this->match(Token::TYPE_LBRACE)) {
247 57
            $fields = $this->parseBody($type == Token::TYPE_TYPED_FRAGMENT ? Token::TYPE_QUERY : $type, false);
248
249 56
            if (!$fields) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fields of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
250 3
                throw $this->createUnexpectedTokenTypeException($this->lookAhead->getType());
251
            }
252
253 55 View Code Duplication
            if ($type == Token::TYPE_QUERY) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
254 53
                return new Query($nameToken->getData(), $alias, $arguments, $fields, $bodyLocation);
255 6
            } elseif ($type == Token::TYPE_TYPED_FRAGMENT) {
256 3
                return new TypedFragmentReference($nameToken->getData(), $fields, $bodyLocation);
257
            } else {
258 3
                return new Mutation($nameToken->getData(), $alias, $arguments, $fields, $bodyLocation);
259
            }
260 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
261 83
            if ($highLevel && $type == Token::TYPE_MUTATION) {
262 9
                return new Mutation($nameToken->getData(), $alias, $arguments, [], $bodyLocation);
263 75
            } elseif ($highLevel && $type == Token::TYPE_QUERY) {
264 24
                return new Query($nameToken->getData(), $alias, $arguments, [], $bodyLocation);
265
            }
266
267 55
            return new Field($nameToken->getData(), $alias, $arguments, $bodyLocation);
268
        }
269
    }
270
271 55
    protected function parseArgumentList()
272
    {
273 55
        $args  = [];
274
275 55
        $this->expect(Token::TYPE_LPAREN);
276
277 55
        while (!$this->match(Token::TYPE_RPAREN) && !$this->end()) {
278 55
            $args[] = $this->parseArgument();
279
        }
280
281 49
        $this->expect(Token::TYPE_RPAREN);
282
283 48
        return $args;
284
    }
285
286 55
    protected function parseArgument()
287
    {
288 55
        $nameToken = $this->eatIdentifierToken();
289 55
        $this->expect(Token::TYPE_COLON);
290 54
        $value = $this->parseValue();
291
292 50
        return new Argument($nameToken->getData(), $value, new Location($nameToken->getLine(), $nameToken->getColumn()));
0 ignored issues
show
Bug introduced by
It seems like $value defined by $this->parseValue() on line 290 can also be of type array; however, Youshido\GraphQL\Parser\...Argument::__construct() does only seem to accept object<Youshido\GraphQL\...erfaces\ValueInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
293
    }
294
295
    /**
296
     * @return array|InputList|InputObject|Literal|VariableReference
297
     *
298
     * @throws SyntaxErrorException
299
     */
300 54
    protected function parseValue()
301
    {
302 54
        switch ($this->lookAhead->getType()) {
303 54
            case Token::TYPE_LSQUARE_BRACE:
304 7
                return $this->parseList();
305
306 47
            case Token::TYPE_LBRACE:
307 8
                return $this->parseObject();
308
309 39
            case Token::TYPE_VARIABLE:
310 11
                return $this->parseVariableReference();
311
312 33
            case Token::TYPE_NUMBER:
313 25
            case Token::TYPE_STRING:
314 11
            case Token::TYPE_IDENTIFIER:
315 8
            case Token::TYPE_NULL:
316 5
            case Token::TYPE_TRUE:
317 1
            case Token::TYPE_FALSE:
318 32
                $token = $this->lex();
319
320 32
                return new Literal($token->getData(), new Location($token->getLine(), $token->getColumn()));
321
        }
322
323 1
        throw $this->createUnexpectedException($this->lookAhead);
324
    }
325
326 9
    protected function parseList($createType = true)
327
    {
328 9
        $startToken = $this->eat(Token::TYPE_LSQUARE_BRACE);
329
330 9
        $list = [];
331 9
        while (!$this->match(Token::TYPE_RSQUARE_BRACE) && !$this->end()) {
332 9
            $list[] = $this->parseListValue();
333
        }
334
335 8
        $this->expect(Token::TYPE_RSQUARE_BRACE);
336
337 8
        return $createType ? new InputList($list, new Location($startToken->getLine(), $startToken->getColumn())) : $list;
338
    }
339
340 15
    protected function parseListValue()
341
    {
342 15
        switch ($this->lookAhead->getType()) {
343 15
            case Token::TYPE_NUMBER:
344 11
            case Token::TYPE_STRING:
345 9
            case Token::TYPE_TRUE:
346 9
            case Token::TYPE_FALSE:
347 9
            case Token::TYPE_NULL:
348 6
            case Token::TYPE_IDENTIFIER:
349 14
                return $this->expect($this->lookAhead->getType())->getData();
350
351 5
            case Token::TYPE_VARIABLE:
352
                return $this->parseVariableReference();
353
354 5
            case Token::TYPE_LBRACE:
355 4
                return $this->parseObject(true);
356
357 3
            case Token::TYPE_LSQUARE_BRACE:
358 2
                return $this->parseList(false);
359
        }
360
361 1
        throw new SyntaxErrorException('Can\'t parse argument', $this->getLocation());
362
    }
363
364 9
    protected function parseObject($createType = true)
365
    {
366 9
        $startToken = $this->eat(Token::TYPE_LBRACE);
367
368 9
        $object = [];
369 9
        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
370 9
            $key = $this->expectMulti([Token::TYPE_STRING, Token::TYPE_IDENTIFIER])->getData();
371 9
            $this->expect(Token::TYPE_COLON);
372 9
            $value = $this->parseListValue();
373
374 9
            $object[$key] = $value;
375
        }
376
377 7
        $this->eat(Token::TYPE_RBRACE);
378
379 7
        return $createType ? new InputObject($object, new Location($startToken->getLine(), $startToken->getColumn())) : $object;
380
    }
381
382 8
    protected function parseFragment()
383
    {
384 8
        $this->lex();
385 8
        $nameToken = $this->eatIdentifierToken();
386
387 8
        $this->eat(Token::TYPE_ON);
388
389 8
        $model  = $this->eatIdentifierToken();
390 8
        $fields = $this->parseBody(Token::TYPE_QUERY, false);
391
392 8
        return new Fragment($nameToken->getData(), $model->getData(), $fields, new Location($nameToken->getLine(), $nameToken->getColumn()));
393
    }
394
395 94
    protected function eat($type)
396
    {
397 94
        if ($this->match($type)) {
398 47
            return $this->lex();
399
        }
400
401 89
        return null;
402
    }
403
404
    protected function eatMulti($types)
405
    {
406
        if ($this->matchMulti($types)) {
407
            return $this->lex();
408
        }
409
410
        return null;
411
    }
412
413 92
    protected function matchMulti($types)
414
    {
415 92
        foreach ($types as $type) {
416 92
            if ($this->peek()->getType() == $type) {
417 92
                return true;
418
            }
419
        }
420
421 2
        return false;
422
    }
423
}
424