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!=, orswitchconditions), values of different types might be equal.For
stringvalues, the empty string''is a special case, in particular the following results might be unexpected: