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 AbstractOperationRule 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 AbstractOperationRule, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
12 | abstract class AbstractOperationRule extends AbstractRule |
||
13 | { |
||
14 | /** |
||
15 | * This property should never be null. |
||
16 | * |
||
17 | * @var array<AbstractRule> $operands |
||
18 | */ |
||
19 | protected $operands = []; |
||
20 | |||
21 | const remove_negations = 'remove_negations'; |
||
22 | const rootify_disjunctions = 'rootify_disjunctions'; |
||
23 | const unify_atomic_operands = 'unify_atomic_operands'; |
||
24 | const remove_invalid_branches = 'remove_invalid_branches'; // simplified after this step |
||
25 | |||
26 | const simplified = self::remove_invalid_branches; |
||
27 | |||
28 | /** |
||
29 | * The order is important! |
||
30 | * |
||
31 | * @var array $simplification_steps |
||
32 | */ |
||
33 | const simplification_steps = [ |
||
34 | AbstractOperationRule::remove_negations, |
||
35 | AbstractOperationRule::rootify_disjunctions, |
||
36 | AbstractOperationRule::unify_atomic_operands, |
||
37 | AbstractOperationRule::remove_invalid_branches, |
||
38 | ]; |
||
39 | |||
40 | /** |
||
41 | * @var null|string $simplified |
||
42 | */ |
||
43 | protected $current_simplification_step = null; |
||
44 | |||
45 | /** |
||
46 | */ |
||
47 | 288 | public function __construct( array $operands=[] ) |
|
52 | |||
53 | /** |
||
54 | * @return bool |
||
55 | */ |
||
56 | public function isSimplified() |
||
60 | |||
61 | /** |
||
62 | * Adds an operand to the logical operation (&& or ||). |
||
63 | * |
||
64 | * @param AbstractRule $new_operand |
||
65 | * |
||
66 | * @return $this |
||
67 | */ |
||
68 | 293 | public function addOperand( AbstractRule $new_operand ) |
|
82 | |||
83 | /** |
||
84 | * @return array |
||
85 | */ |
||
86 | 282 | public function getOperands() |
|
90 | |||
91 | /** |
||
92 | * @return $this |
||
93 | */ |
||
94 | 291 | public function setOperands(array $operands) |
|
103 | |||
104 | /** |
||
105 | * @param array|callable $renamings Associative array of renamings or callable |
||
106 | * that would rename the fields. |
||
107 | * |
||
108 | * @return $this |
||
109 | */ |
||
110 | 1 | public function renameFields($renamings) |
|
115 | |||
116 | /** |
||
117 | * @param array|callable $renamings Associative array of renamings or callable |
||
118 | * that would rename the fields. |
||
119 | * |
||
120 | * @return boolean Whether or not the operation changed semantically |
||
121 | */ |
||
122 | 1 | public function renameFields_andReturnIsChanged($renamings) |
|
141 | |||
142 | /** |
||
143 | * @param string $step_to_go_to |
||
144 | * @param array $simplification_options |
||
145 | * @param bool $force |
||
146 | */ |
||
147 | 152 | public function moveSimplificationStepForward($step_to_go_to, array $simplification_options, $force=false) |
|
177 | |||
178 | /** |
||
179 | * @return string The current simplification step |
||
180 | */ |
||
181 | 57 | public function getSimplificationStep() |
|
185 | |||
186 | /** |
||
187 | * Checks if a simplification step is reached. |
||
188 | * |
||
189 | * @param string $step |
||
190 | * |
||
191 | * @return bool |
||
192 | */ |
||
193 | 68 | public function simplicationStepReached($step) |
|
212 | |||
213 | /** |
||
214 | * Replace NotRule objects by the negation of their operands. |
||
215 | * |
||
216 | * @return AbstractOperationRule $this or a $new rule with negations removed |
||
217 | */ |
||
218 | 152 | public function removeNegations(array $contextual_options) |
|
243 | |||
244 | /** |
||
245 | * Operation cleaning consists of removing operation with one operand |
||
246 | * and removing operations having a same type of operation as operand. |
||
247 | * |
||
248 | * This operation has been required between every steps until now. |
||
249 | * |
||
250 | * @toopt Trigger cleaning requirement during simplification steps |
||
251 | * |
||
252 | * @param array $simplification_options |
||
253 | * @param bool $recurse |
||
254 | * |
||
255 | * @return AbstractOperationRule |
||
256 | */ |
||
257 | 152 | public function cleanOperations(array $simplification_options, $recurse=true) |
|
290 | |||
291 | /** |
||
292 | * If a child is an OrRule or an AndRule and has only one child, |
||
293 | * replace it by its child. |
||
294 | * |
||
295 | * @used-by removeSameOperationOperands() Ping-pong recursion |
||
296 | * |
||
297 | * @return bool If something has been simplified or not |
||
298 | */ |
||
299 | 152 | public function removeMonooperandOperationsOperands(array $simplification_options) |
|
325 | |||
326 | /** |
||
327 | * Removes duplicates between the current AbstractOperationRule. |
||
328 | * |
||
329 | * @return AbstractOperationRule the simplified rule |
||
330 | */ |
||
331 | 152 | public function unifyAtomicOperands($simplification_strategy_step = false, array $contextual_options) |
|
380 | |||
381 | private static $simplification_cache = []; |
||
382 | |||
383 | /** |
||
384 | * Simplify the current OperationRule. |
||
385 | * + If an OrRule or an AndRule contains only one operand, it's equivalent |
||
386 | * to it. |
||
387 | * + If an OrRule has an other OrRule as operand, they can be merged |
||
388 | * + If an AndRule has an other AndRule as operand, they can be merged |
||
389 | * |
||
390 | * @param array $options stop_after | stop_before | force_logical_core |
||
391 | * |
||
392 | * @return AbstractRule the simplified rule |
||
393 | */ |
||
394 | 152 | final public function simplify($options=[]) |
|
501 | |||
502 | /** |
||
503 | * Indexes operands by their fields and operators. This sorting is |
||
504 | * used during the simplification step. |
||
505 | * |
||
506 | * @return array The 3 dimensions array of operands: field > operator > i |
||
507 | */ |
||
508 | 141 | View Code Duplication | public function groupOperandsByFieldAndOperator() |
532 | |||
533 | /** |
||
534 | * Indexes operands by their fields and operators. This sorting is |
||
535 | * used during the simplification step. |
||
536 | * |
||
537 | * @return array The 3 dimensions array of operands: field > operator > i |
||
538 | */ |
||
539 | 152 | View Code Duplication | protected static function groupOperandsByFieldAndOperator_static($operands) |
563 | |||
564 | /** |
||
565 | * Clones the rule and its operands. |
||
566 | * |
||
567 | * @return AbstractOperationRule A copy of the current instance with copied operands. |
||
568 | */ |
||
569 | 156 | final public function copy() |
|
573 | |||
574 | /** |
||
575 | * Make a deep copy of operands |
||
576 | */ |
||
577 | 156 | public function __clone() |
|
583 | |||
584 | /** |
||
585 | */ |
||
586 | 152 | public function isNormalizationAllowed(array $current_simplification_options) |
|
590 | |||
591 | /** |
||
592 | * Returns an operand based on its position |
||
593 | * |
||
594 | * @return AbstractRule|null The operand if it exists or null |
||
595 | */ |
||
596 | 98 | protected function getOperandAt($index=0) |
|
603 | |||
604 | /**/ |
||
605 | } |
||
606 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: