Completed
Push — master ( d9e3ed...39b3dc )
by Portey
02:49
created

Parser   F

Complexity

Total Complexity 76

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 99.49%

Importance

Changes 14
Bugs 3 Features 7
Metric Value
wmc 76
c 14
b 3
f 7
lcom 1
cbo 14
dl 0
loc 334
ccs 195
cts 196
cp 0.9949
rs 3.5632

20 Methods

Rating   Name   Duplication   Size   Complexity  
B parse() 0 28 6
C parseBody() 0 36 7
A expect() 0 8 2
A expectMulti() 0 8 2
A parseReference() 0 10 3
A parseFragmentReference() 0 6 1
A parseIdentifier() 0 9 1
C parseBodyItem() 0 30 8
A parseArgumentList() 0 21 4
A parseArgument() 0 8 1
C parseValue() 0 29 12
B parseList() 0 17 5
D parseListValue() 0 30 9
B parseObject() 0 21 5
A parseVariable() 0 8 1
A parseFragment() 0 12 1
A eat() 0 8 2
A eatMulti() 0 8 2
A matchMulti() 0 10 3
A match() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Parser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Parser, and based on these observations, apply Extract Interface, too.

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
use Youshido\GraphQL\Parser\Ast\TypedFragment;
12
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;
13
use Youshido\GraphQL\Parser\Value\InputList;
14
use Youshido\GraphQL\Parser\Ast\Argument;
15
use Youshido\GraphQL\Parser\Ast\Field;
16
use Youshido\GraphQL\Parser\Ast\Fragment;
17
use Youshido\GraphQL\Parser\Ast\FragmentReference;
18
use Youshido\GraphQL\Parser\Value\InputObject;
19
use Youshido\GraphQL\Parser\Value\Literal;
20
use Youshido\GraphQL\Parser\Ast\Mutation;
21
use Youshido\GraphQL\Parser\Ast\Query;
22
use Youshido\GraphQL\Parser\Value\Variable;
23
24
class Parser extends Tokenizer
25
{
26
27 45
    public function parse()
28
    {
29 45
        $data = ['queries' => [], 'mutations' => [], 'fragments' => []];
30
31 45
        while (!$this->end()) {
32 45
            $tokenType = $this->peek()->getType();
33
34
            switch ($tokenType) {
35 45
                case Token::TYPE_LBRACE:
36 45
                case Token::TYPE_QUERY:
37 39
                    $data = array_merge($data, ['queries' => $this->parseBody()]);
38 33
                    break;
39
40 9
                case Token::TYPE_MUTATION:
41 5
                    $data = array_merge($data, ['mutations' => $this->parseBody(Token::TYPE_MUTATION)]);
42 3
                    break;
43
44 4
                case Token::TYPE_FRAGMENT:
45 3
                    $data['fragments'][] = $this->parseFragment();
46 3
                    break;
47
48 1
                default:
49 1
                    throw new SyntaxErrorException();
50 1
            }
51 36
        }
52
53 36
        return $data;
54
    }
55
56 44
    protected function parseBody($token = Token::TYPE_QUERY, $highLevel = true)
57
    {
58 44
        $fields = [];
59 44
        $first  = true;
60
61 44
        if ($this->peek()->getType() == $token) {
62 14
            $this->lex();
63 14
            $this->eat(Token::TYPE_IDENTIFIER);
64 14
        }
65
66 44
        $this->lex();
67
68 44
        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
69 41
            if ($first) {
70 41
                $first = false;
71 41
            } else {
72 18
                $this->eatMulti([Token::TYPE_COMMA]);
73
            }
74
75 41
            if ($this->match(Token::TYPE_FRAGMENT_REFERENCE)) {
76 6
                $this->lex();
77
78 6
                if ($this->eat(Token::TYPE_ON)) {
79 3
                    $fields[] = $this->parseBodyItem(Token::TYPE_TYPED_FRAGMENT, $highLevel);
80 3
                } else {
81 3
                    $fields[] = $this->parseFragmentReference();
82
                }
83 6
            } else {
84 41
                $fields[] = $this->parseBodyItem($token, $highLevel);
85
            }
86 36
        }
87
88 37
        $this->expect(Token::TYPE_RBRACE);
89
90 37
        return $fields;
91
    }
92
93 42
    protected function expect($type)
94
    {
95 42
        if ($this->match($type)) {
96 42
            return $this->lex();
97
        }
98
99 3
        throw $this->createUnexpected($this->peek());
100
    }
101
102 41
    protected function expectMulti($types)
103
    {
104 41
        if ($this->matchMulti($types)) {
105 41
            return $this->lex();
106
        }
107
108 2
        throw $this->createUnexpected($this->peek());
109
    }
110
111 4
    protected function parseReference()
112
    {
113 4
        $this->expectMulti([Token::TYPE_AMP, Token::TYPE_VARIABLE]);
114
115 4
        if ($this->match(Token::TYPE_NUMBER) || $this->match(Token::TYPE_IDENTIFIER)) {
116 3
            return new Variable($this->lex()->getData());
117
        }
118
119 1
        throw $this->createUnexpected($this->peek());
120
    }
121
122 3
    protected function parseFragmentReference()
123
    {
124 3
        $name = $this->parseIdentifier();
125
126 3
        return new FragmentReference($name);
127
    }
128
129 41
    protected function parseIdentifier()
130
    {
131 41
        return $this->expectMulti([
132 41
            Token::TYPE_IDENTIFIER,
133 41
            Token::TYPE_MUTATION,
134 41
            Token::TYPE_QUERY,
135 41
            Token::TYPE_FRAGMENT,
136 41
        ])->getData();
137
    }
138
139 41
    protected function parseBodyItem($type = Token::TYPE_QUERY, $highLevel = true)
140
    {
141 41
        $name  = $this->parseIdentifier();
142 41
        $alias = null;
143
144 41
        if ($this->eat(Token::TYPE_COLON)) {
145 10
            $alias = $name;
146 10
            $name  = $this->parseIdentifier();
147 10
        }
148
149 41
        $arguments = $this->match(Token::TYPE_LPAREN) ? $this->parseArgumentList() : [];
150
151 36
        if ($this->match(Token::TYPE_LBRACE)) {
152 34
            $fields = $this->parseBody($type, false);
153
154 32
            if ($type == Token::TYPE_QUERY) {
155 31
                return new Query($name, $alias, $arguments, $fields);
156 4
            } elseif ($type == Token::TYPE_TYPED_FRAGMENT) {
157 3
                return new TypedFragmentReference($name, $fields);
158
            } else {
159 1
                return new Mutation($name, $alias, $arguments, $fields);
160
            }
161
        } else {
162 36
            if ($highLevel && $type == Token::TYPE_MUTATION) {
163 2
                return new Mutation($name, $alias, $arguments);
164
            }
165
166 34
            return new Field($name, $alias);
167
        }
168
    }
169
170 20
    protected function parseArgumentList()
171
    {
172 20
        $args  = [];
173 20
        $first = true;
174
175 20
        $this->expect(Token::TYPE_LPAREN);
176
177 20
        while (!$this->match(Token::TYPE_RPAREN) && !$this->end()) {
178 20
            if ($first) {
179 20
                $first = false;
180 20
            } else {
181 3
                $this->expect(Token::TYPE_COMMA);
182
            }
183
184 20
            $args[] = $this->parseArgument();
185 15
        }
186
187 15
        $this->expect(Token::TYPE_RPAREN);
188
189 15
        return $args;
190
    }
191
192 20
    protected function parseArgument()
193
    {
194 20
        $name = $this->parseIdentifier();
195 20
        $this->expect(Token::TYPE_COLON);
196 20
        $value = $this->parseValue();
197
198 15
        return new Argument($name, $value);
199
    }
200
201 20
    protected function parseValue()
202
    {
203 20
        switch ($this->lookAhead->getType()) {
204 20
            case Token::TYPE_LSQUARE_BRACE:
205 3
                return $this->parseList();
206
207 17
            case Token::TYPE_LBRACE:
208 3
                return $this->parseObject();
209
210 14
            case Token::TYPE_AMP:
211 14
            case Token::TYPE_VARIABLE:
212 4
                return $this->parseReference();
213
214 11
            case Token::TYPE_LT:
215 1
                return $this->parseVariable();
216
217 10
            case Token::TYPE_NUMBER:
218 10
            case Token::TYPE_STRING:
219 10
            case Token::TYPE_IDENTIFIER:
220 9
                return new Literal($this->lex()->getData());
221
222 2
            case Token::TYPE_NULL:
223 2
            case Token::TYPE_TRUE:
224 2
            case Token::TYPE_FALSE:
225 1
                return new Literal($this->lex()->getData());
226 1
        }
227
228 1
        throw $this->createUnexpected($this->lookAhead);
229
    }
230
231 4
    protected function parseList($createType = true)
232
    {
233 4
        $this->eat(Token::TYPE_LSQUARE_BRACE);
234
235 4
        $list = [];
236 4
        while (!$this->match(Token::TYPE_RSQUARE_BRACE) && !$this->end()) {
237 4
            $list[] = $this->parseListValue();
238
239 3
            if ($this->lookAhead->getType() != Token::TYPE_RSQUARE_BRACE) {
240 3
                $this->expect(Token::TYPE_COMMA);
241 3
            }
242 3
        }
243
244 3
        $this->expect(Token::TYPE_RSQUARE_BRACE);
245
246 3
        return $createType ? new InputList($list) : $list;
247
    }
248
249 6
    protected function parseListValue()
250
    {
251 6
        switch ($this->lookAhead->getType()) {
252 6
            case Token::TYPE_NUMBER:
253 5
                return $this->expect(Token::TYPE_NUMBER)->getData();
254
255 4
            case Token::TYPE_STRING:
256 2
                return $this->expect(Token::TYPE_STRING)->getData();
257
258 4
            case Token::TYPE_LBRACE:
259 1
                return $this->parseObject(false);
260
261 4
            case Token::TYPE_LSQUARE_BRACE:
262 1
                return $this->parseList(false);
263
264 3
            case Token::TYPE_TRUE:
265 1
                return $this->expect(Token::TYPE_TRUE)->getData();
266
267 3
            case Token::TYPE_FALSE:
268 1
                return $this->expect(Token::TYPE_FALSE)->getData();
269
270 3
            case Token::TYPE_NULL:
271 2
                return $this->expect(Token::TYPE_NULL)->getData();
272
273 1
            case Token::TYPE_IDENTIFIER:
274
                return $this->expect(Token::TYPE_IDENTIFIER)->getData();
275 1
        }
276
277 1
        throw new SyntaxErrorException('Can\'t parse argument');
278
    }
279
280 3
    protected function parseObject($createType = true)
281
    {
282 3
        $this->eat(Token::TYPE_LBRACE);
283
284 3
        $object = [];
285 3
        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
286 3
            $key = $this->expect(Token::TYPE_STRING)->getData();
287 3
            $this->expect(Token::TYPE_COLON);
288 3
            $value = $this->parseListValue();
289
290 3
            if ($this->peek()->getType() != Token::TYPE_RBRACE) {
291 3
                $this->expect(Token::TYPE_COMMA);
292 3
            }
293
294 3
            $object[$key] = $value;
295 3
        }
296
297 1
        $this->eat(Token::TYPE_RBRACE);
298
299 1
        return $createType ? new InputObject($object) : $object;
300
    }
301
302 1
    protected function parseVariable()
303
    {
304 1
        $this->expect(Token::TYPE_LT);
305 1
        $name = $this->expect(Token::TYPE_IDENTIFIER)->getData();
306 1
        $this->expect(Token::TYPE_GT);
307
308 1
        return new Variable($name);
309
    }
310
311 3
    protected function parseFragment()
312
    {
313 3
        $this->lex();
314 3
        $name = $this->parseIdentifier();
315
316 3
        $this->eat(Token::TYPE_ON);
317
318 3
        $model  = $this->parseIdentifier();
319 3
        $fields = $this->parseBody();
320
321 3
        return new Fragment($name, $model, $fields);
322
    }
323
324 43
    protected function eat($type)
325
    {
326 43
        if ($this->match($type)) {
327 21
            return $this->lex();
328
        }
329
330 42
        return null;
331
    }
332
333 18
    protected function eatMulti($types)
334
    {
335 18
        if ($this->matchMulti($types)) {
336 16
            return $this->lex();
337
        }
338
339 2
        return null;
340
    }
341
342 41
    protected function matchMulti($types)
343
    {
344 41
        foreach ($types as $type) {
345 41
            if ($this->peek()->getType() == $type) {
346 41
                return true;
347
            }
348 9
        }
349
350 4
        return false;
351
    }
352
353 44
    protected function match($type)
354
    {
355 44
        return $this->peek()->getType() === $type;
356
    }
357
}