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 BooleanParser 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 BooleanParser, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
33 | class BooleanParser |
||
34 | { |
||
35 | |||
36 | /** |
||
37 | * List of comparators to check in the parseCompareToken if the current |
||
38 | * part of the expression is a comparator and needs to be compared |
||
39 | */ |
||
40 | const COMPARATORS = '==,===,!==,!=,<=,>=,<,>,%'; |
||
41 | |||
42 | /** |
||
43 | * Regex to parse a expression into tokens |
||
44 | */ |
||
45 | const TOKENREGEX = '/ |
||
46 | \s*( |
||
47 | \\\\\' |
||
48 | | |
||
49 | \\" |
||
50 | | |
||
51 | [\'"] |
||
52 | | |
||
53 | [A-Za-z0-9\.\{\}\-\\\\]+ |
||
54 | | |
||
55 | \=\=\= |
||
56 | | |
||
57 | \=\= |
||
58 | | |
||
59 | !\=\= |
||
60 | | |
||
61 | !\= |
||
62 | | |
||
63 | <\= |
||
64 | | |
||
65 | >\= |
||
66 | | |
||
67 | < |
||
68 | | |
||
69 | > |
||
70 | | |
||
71 | % |
||
72 | | |
||
73 | \|\| |
||
74 | | |
||
75 | [aA][nN][dD] |
||
76 | | |
||
77 | && |
||
78 | | |
||
79 | [oO][rR] |
||
80 | | |
||
81 | .? |
||
82 | )\s* |
||
83 | /xsu'; |
||
84 | |||
85 | /** |
||
86 | * Cursor that contains a integer value pointing to the location inside the |
||
87 | * expression string that is used by the peek function to look for the part of |
||
88 | * the expression that needs to be focused on next. This cursor is changed |
||
89 | * by the consume method, by "consuming" part of the expression. |
||
90 | * |
||
91 | * @var integer |
||
92 | */ |
||
93 | protected $cursor = 0; |
||
94 | |||
95 | /** |
||
96 | * Expression that is parsed through peek and consume methods |
||
97 | * |
||
98 | * @var string |
||
99 | */ |
||
100 | protected $expression; |
||
101 | |||
102 | /** |
||
103 | * Context containing all variables that are references in the expression |
||
104 | * |
||
105 | * @var array |
||
106 | */ |
||
107 | protected $context; |
||
108 | |||
109 | /** |
||
110 | * Switch to enable compiling |
||
111 | * |
||
112 | * @var boolean |
||
113 | */ |
||
114 | protected $compileToCode = false; |
||
115 | |||
116 | /** |
||
117 | * Evaluate a expression to a boolean |
||
118 | * |
||
119 | * @param string $expression to be parsed |
||
120 | * @param array $context containing variables that can be used in the expression |
||
121 | * @return boolean |
||
122 | */ |
||
123 | public function evaluate($expression, $context) |
||
130 | |||
131 | /** |
||
132 | * Parse and compile an expression into an php equivalent |
||
133 | * |
||
134 | * @param string $expression to be parsed |
||
135 | * @return string |
||
136 | */ |
||
137 | public function compile($expression) |
||
144 | |||
145 | /** |
||
146 | * The part of the expression we're currently focusing on based on the |
||
147 | * tokenizing regex offset by the internally tracked cursor. |
||
148 | * |
||
149 | * @param boolean $includeWhitespace return surrounding whitespace with token |
||
150 | * @return string |
||
151 | */ |
||
152 | protected function peek($includeWhitespace = false) |
||
160 | |||
161 | /** |
||
162 | * Consume part of the current expression by setting the internal cursor |
||
163 | * to the position of the string in the expression and it's length |
||
164 | * |
||
165 | * @param string $string |
||
166 | * @return void |
||
167 | */ |
||
168 | protected function consume($string) |
||
175 | |||
176 | /** |
||
177 | * Passes the torch down to the next deeper parsing leve (and) |
||
178 | * and checks then if there's a "or" expression that needs to be handled |
||
179 | * |
||
180 | * @return mixed |
||
181 | */ |
||
182 | View Code Duplication | protected function parseOrToken() |
|
197 | |||
198 | /** |
||
199 | * Passes the torch down to the next deeper parsing leve (compare) |
||
200 | * and checks then if there's a "and" expression that needs to be handled |
||
201 | * |
||
202 | * @return mixed |
||
203 | */ |
||
204 | View Code Duplication | protected function parseAndToken() |
|
219 | |||
220 | /** |
||
221 | * Passes the torch down to the next deeper parsing leven (not) |
||
222 | * and checks then if there's a "compare" expression that needs to be handled |
||
223 | * |
||
224 | * @return mixed |
||
225 | */ |
||
226 | protected function parseCompareToken() |
||
236 | |||
237 | /** |
||
238 | * Check if we have encountered an not expression or pass the torch down |
||
239 | * to the simpleToken method. |
||
240 | * |
||
241 | * @return mixed |
||
242 | */ |
||
243 | protected function parseNotToken() |
||
257 | |||
258 | /** |
||
259 | * Takes care of restarting the whole parsing loop if it encounters a "(" or ")" |
||
260 | * token or pass the torch down to the parseStringToken method |
||
261 | * |
||
262 | * @return mixed |
||
263 | */ |
||
264 | protected function parseBracketToken() |
||
276 | |||
277 | /** |
||
278 | * Takes care of consuming pure string including whitespace or passes the torch |
||
279 | * down to the parseTermToken method |
||
280 | * |
||
281 | * @return mixed |
||
282 | */ |
||
283 | protected function parseStringToken() |
||
304 | |||
305 | /** |
||
306 | * Takes care of restarting the whole parsing loop if it encounters a "(" or ")" |
||
307 | * token, consumes a pure string including whitespace or passes the torch |
||
308 | * down to the evaluateTerm method |
||
309 | * |
||
310 | * @return mixed |
||
311 | */ |
||
312 | protected function parseTermToken() |
||
318 | |||
319 | /** |
||
320 | * Evaluate an "and" comparison |
||
321 | * |
||
322 | * @param mixed $x |
||
323 | * @param mixed $y |
||
324 | * @return boolean |
||
325 | */ |
||
326 | protected function evaluateAnd($x, $y) |
||
330 | |||
331 | /** |
||
332 | * Evaluate an "or" comparison |
||
333 | * |
||
334 | * @param mixed $x |
||
335 | * @param mixed $y |
||
336 | * @return boolean |
||
337 | */ |
||
338 | protected function evaluateOr($x, $y) |
||
342 | |||
343 | /** |
||
344 | * Evaluate an "not" comparison |
||
345 | * |
||
346 | * @param mixed $x |
||
347 | * @return boolean|string |
||
348 | */ |
||
349 | protected function evaluateNot($x) |
||
353 | |||
354 | /** |
||
355 | * Compare two variables based on a specified comparator |
||
356 | * |
||
357 | * @param mixed $x |
||
358 | * @param mixed $y |
||
359 | * @param string $comparator |
||
360 | * @return boolean|string |
||
361 | */ |
||
362 | protected function evaluateCompare($x, $y, $comparator) |
||
415 | |||
416 | /** |
||
417 | * Takes care of fetching terms from the context, converting to float/int, |
||
418 | * converting true/false keywords into boolean or trim the final string of |
||
419 | * quotation marks |
||
420 | * |
||
421 | * @param string $x |
||
422 | * @param array $context |
||
423 | * @return mixed |
||
424 | */ |
||
425 | protected function evaluateTerm($x, $context) |
||
464 | |||
465 | public static function convertNodeToBoolean($value) { |
||
471 | } |
||
472 |
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.