Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
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 |
||
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 = []) |
|
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) |
|
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) |
|
104 | |||
105 | |||
106 | /** |
||
107 | * Moves the internal parser object to the next lexed token. |
||
108 | */ |
||
109 | 309 | protected function advance() |
|
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) |
|
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) |
|
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) |
|
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) |
|
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) |
||
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) |
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) |
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() |
|
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() |
|
277 | |||
278 | /** |
||
279 | * @return \Fubhy\GraphQL\Language\Node\Document |
||
280 | * |
||
281 | * @throws \Exception |
||
282 | */ |
||
283 | 309 | protected function parseDocument() |
|
307 | |||
308 | /** |
||
309 | * @return \Fubhy\GraphQL\Language\Node\OperationDefinition |
||
310 | */ |
||
311 | 309 | protected function parseOperationDefinition() |
|
333 | |||
334 | /** |
||
335 | * @return \Fubhy\GraphQL\Language\Node\VariableDefinition[] |
||
336 | */ |
||
337 | 225 | protected function parseVariableDefinitions() |
|
341 | |||
342 | /** |
||
343 | * @return \Fubhy\GraphQL\Language\Node\VariableDefinition |
||
344 | */ |
||
345 | 72 | protected function parseVariableDefinition() |
|
358 | |||
359 | /** |
||
360 | * @return \Fubhy\GraphQL\Language\Node\Variable |
||
361 | */ |
||
362 | 78 | protected function parseVariable() { |
|
368 | |||
369 | /** |
||
370 | * @return \Fubhy\GraphQL\Language\Node\SelectionSet |
||
371 | */ |
||
372 | 309 | protected function parseSelectionSet() |
|
381 | |||
382 | /** |
||
383 | * @return \Fubhy\GraphQL\Language\Node\SelectionInterface |
||
384 | */ |
||
385 | 309 | protected function parseSelection() |
|
389 | |||
390 | /** |
||
391 | * @return \Fubhy\GraphQL\Language\Node\Field |
||
392 | */ |
||
393 | 309 | protected function parseField() |
|
411 | |||
412 | /** |
||
413 | * @return \Fubhy\GraphQL\Language\Node\Argument[] |
||
414 | */ |
||
415 | 309 | protected function parseArguments() |
|
419 | |||
420 | /** |
||
421 | * @return \Fubhy\GraphQL\Language\Node\Argument |
||
422 | */ |
||
423 | 168 | protected function parseArgument() |
|
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() |
|
461 | |||
462 | /** |
||
463 | * @return \Fubhy\GraphQL\Language\Node\FragmentDefinition |
||
464 | */ |
||
465 | 36 | protected function parseFragmentDefinition() |
|
481 | |||
482 | /** |
||
483 | * @return \Fubhy\GraphQL\Language\Node\ValueInterface |
||
484 | */ |
||
485 | 9 | protected function parseVariableValue() |
|
489 | |||
490 | /** |
||
491 | * @return \Fubhy\GraphQL\Language\Node\ValueInterface |
||
492 | */ |
||
493 | protected function parseConstValue() |
||
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) |
|
541 | |||
542 | /** |
||
543 | * @param bool $isConst |
||
544 | * |
||
545 | * @return \Fubhy\GraphQL\Language\Node\ArrayValue |
||
546 | */ |
||
547 | 9 | protected function parseArray($isConst) |
|
557 | |||
558 | /** |
||
559 | * @param bool $isConst |
||
560 | * |
||
561 | * @return \Fubhy\GraphQL\Language\Node\ObjectValue |
||
562 | */ |
||
563 | 9 | protected function parseObject($isConst) |
|
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) |
|
600 | |||
601 | /** |
||
602 | * @return \Fubhy\GraphQL\Language\Node\Directive[] |
||
603 | */ |
||
604 | 309 | protected function parseDirectives() |
|
613 | |||
614 | /** |
||
615 | * @return \Fubhy\GraphQL\Language\Node\Directive |
||
616 | * |
||
617 | * @throws \Exception |
||
618 | */ |
||
619 | 15 | protected function parseDirective() |
|
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() |
|
656 | |||
657 | /** |
||
658 | * @return \Fubhy\GraphQL\Language\Node\NamedType |
||
659 | */ |
||
660 | 108 | protected function parseNamedType() |
|
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.