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 | 156 | 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 | 156 | public function addOperand( AbstractRule $new_operand ) |
|
82 | |||
83 | /** |
||
84 | * @return array |
||
85 | */ |
||
86 | 151 | public function getOperands() |
|
90 | |||
91 | /** |
||
92 | * @return $this |
||
93 | */ |
||
94 | 157 | 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 string $this |
||
109 | */ |
||
110 | 1 | public function renameFields($renamings) |
|
126 | |||
127 | /** |
||
128 | * @param string $step_to_go_to |
||
129 | * @param array $simplification_options |
||
130 | * @param bool $force |
||
131 | */ |
||
132 | 102 | public function moveSimplificationStepForward($step_to_go_to, array $simplification_options, $force=false) |
|
162 | |||
163 | /** |
||
164 | * @return string The current simplification step |
||
165 | */ |
||
166 | 28 | public function getSimplificationStep() |
|
170 | |||
171 | /** |
||
172 | * Checks if a simplification step is reached. |
||
173 | * |
||
174 | * @param string $step |
||
175 | * |
||
176 | * @return bool |
||
177 | */ |
||
178 | 42 | public function simplicationStepReached($step) |
|
197 | |||
198 | /** |
||
199 | * Replace NotRule objects by the negation of their operands. |
||
200 | * |
||
201 | * @return AbstractOperationRule $this or a $new rule with negations removed |
||
202 | */ |
||
203 | 99 | public function removeNegations(array $contextual_options) |
|
228 | |||
229 | /** |
||
230 | * Operation cleaning consists of removing operation with one operand |
||
231 | * and removing operations having a same type of operation as operand. |
||
232 | * |
||
233 | * This operation has been required between every steps until now. |
||
234 | * |
||
235 | * @toopt Trigger cleaning requirement during simplification steps |
||
236 | * |
||
237 | * @param array $simplification_options |
||
238 | * @param bool $recurse |
||
239 | * |
||
240 | * @return AbstractOperationRule |
||
241 | */ |
||
242 | 99 | public function cleanOperations(array $simplification_options, $recurse=true) |
|
275 | |||
276 | /** |
||
277 | * If a child is an OrRule or an AndRule and has only one child, |
||
278 | * replace it by its child. |
||
279 | * |
||
280 | * @used-by removeSameOperationOperands() Ping-pong recursion |
||
281 | * |
||
282 | * @return bool If something has been simplified or not |
||
283 | */ |
||
284 | 99 | public function removeMonooperandOperationsOperands(array $simplification_options) |
|
310 | |||
311 | /** |
||
312 | * Removes duplicates between the current AbstractOperationRule. |
||
313 | * |
||
314 | * @return AbstractOperationRule the simplified rule |
||
315 | */ |
||
316 | 99 | public function unifyAtomicOperands($simplification_strategy_step = false, array $contextual_options) |
|
365 | |||
366 | private static $simplification_cache = []; |
||
367 | |||
368 | /** |
||
369 | * Simplify the current OperationRule. |
||
370 | * + If an OrRule or an AndRule contains only one operand, it's equivalent |
||
371 | * to it. |
||
372 | * + If an OrRule has an other OrRule as operand, they can be merged |
||
373 | * + If an AndRule has an other AndRule as operand, they can be merged |
||
374 | * |
||
375 | * @param array $options stop_after | stop_before | force_logical_core |
||
376 | * |
||
377 | * @return AbstractRule the simplified rule |
||
378 | */ |
||
379 | 102 | final public function simplify($options=[]) |
|
488 | |||
489 | /** |
||
490 | * Indexes operands by their fields and operators. This sorting is |
||
491 | * used during the simplification step. |
||
492 | * |
||
493 | * @return array The 3 dimensions array of operands: field > operator > i |
||
494 | */ |
||
495 | 93 | View Code Duplication | public function groupOperandsByFieldAndOperator() |
519 | |||
520 | /** |
||
521 | * Indexes operands by their fields and operators. This sorting is |
||
522 | * used during the simplification step. |
||
523 | * |
||
524 | * @return array The 3 dimensions array of operands: field > operator > i |
||
525 | */ |
||
526 | 99 | View Code Duplication | protected static function groupOperandsByFieldAndOperator_static($operands) |
550 | |||
551 | /** |
||
552 | * Clones the rule and its operands. |
||
553 | * |
||
554 | * @return AbstractOperationRule A copy of the current instance with copied operands. |
||
555 | */ |
||
556 | 109 | final public function copy() |
|
560 | |||
561 | /** |
||
562 | * Make a deep copy of operands |
||
563 | */ |
||
564 | 109 | public function __clone() |
|
570 | |||
571 | /** |
||
572 | */ |
||
573 | 101 | public function isNormalizationAllowed(array $current_simplification_options) |
|
577 | |||
578 | /** |
||
579 | * Returns an operand based on its position |
||
580 | * |
||
581 | * @return AbstractRule|null The operand if it exists or null |
||
582 | */ |
||
583 | 51 | protected function getOperandAt($index=0) |
|
590 | |||
591 | /**/ |
||
592 | } |
||
593 |
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: