Complex classes like Range 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 Range, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 50 | class Range implements IComparable, \ArrayAccess |
||
|
|
|||
| 51 | { |
||
| 52 | /** @var bool */ |
||
| 53 | private $empty; |
||
| 54 | /** @var mixed */ |
||
| 55 | private $lower; |
||
| 56 | /** @var mixed */ |
||
| 57 | private $upper; |
||
| 58 | /** @var bool */ |
||
| 59 | private $lowerInc; |
||
| 60 | /** @var bool */ |
||
| 61 | private $upperInc; |
||
| 62 | /** @var IValueComparator */ |
||
| 63 | private $comparator; |
||
| 64 | /** @var IDiscreteStepper */ |
||
| 65 | private $discreteStepper; |
||
| 66 | |||
| 67 | |||
| 68 | //region construction |
||
| 69 | |||
| 70 | /** |
||
| 71 | * Creates a new range with given lower and upper bounds. |
||
| 72 | * |
||
| 73 | * There are two ways of calling this factory method: whether the bounds are inclusive or exclusive may be |
||
| 74 | * specified: |
||
| 75 | * - either by passing a specification string as the `$boundsOrLowerInc` argument (e.g., `'[)'`), |
||
| 76 | * - or by passing two boolean values as the `$boundsOrLowerInc` and `$upperInc` telling whichever bound is |
||
| 77 | * inclusive. |
||
| 78 | * |
||
| 79 | * When the constructed range is effectively empty, an empty range gets created, forgetting the given lower and |
||
| 80 | * upper bound (just as PostgreSQL does). That happens in one of the following cases: |
||
| 81 | * * the lower bound is greater than the upper bound, or |
||
| 82 | * * both bounds are equal but any of them is exclusive, or |
||
| 83 | * * the subtype is discrete, the upper bound is just one step after the lower bound and both the bounds are |
||
| 84 | * exclusive. |
||
| 85 | * |
||
| 86 | * For creating an empty range explicitly, see {@link Range::empty()}. |
||
| 87 | * |
||
| 88 | * @param mixed $lower the range lower bound, or <tt>null</tt> if unbounded |
||
| 89 | * @param mixed $upper the range upper bound, or <tt>null</tt> if unbounded |
||
| 90 | * @param bool|string $boundsOrLowerInc |
||
| 91 | * either the string of complete bounds specification, or a boolean telling whether the |
||
| 92 | * lower bound is inclusive; |
||
| 93 | * the complete specification is similar to PostgreSQL - it is a two-character string of |
||
| 94 | * <tt>'['</tt> or <tt>'('</tt>, and <tt>']'</tt> or <tt>')'</tt> (brackets denoting an |
||
| 95 | * inclusive bound, parentheses denoting an exclusive bound; |
||
| 96 | * when the boolean is used, also the <tt>$upperInc</tt> argument is used; |
||
| 97 | * either bound specification is irrelevant if the corresponding range edge is |
||
| 98 | * <tt>null</tt> - the range is open by definition on that side, and thus will be created |
||
| 99 | * as such |
||
| 100 | * @param bool|null $upperInc whether the upper bound is inclusive; |
||
| 101 | * only relevant if <tt>$boundsOrLowerInc</tt> is also boolean |
||
| 102 | * @param IValueComparator|null $customComparator |
||
| 103 | * a custom comparator to use for the values; |
||
| 104 | * skip with <tt>null</tt> to use |
||
| 105 | * {@link Ivory::getDefaultValueComparator() the default value comparator} |
||
| 106 | * @param IDiscreteStepper|null $customDiscreteStepper |
||
| 107 | * a custom discrete stepper, used for converting from/to inclusive/exclusive bounds; |
||
| 108 | * if not given, it is inferred from the lower or upper bound value, whichever is non-null: |
||
| 109 | * <ul> |
||
| 110 | * <li>if the value object implements {@link IDiscreteStepper}, it is used directly; |
||
| 111 | * <li>if the value is an integer, an {@link IntegerStepper} is used; |
||
| 112 | * <li>if an integer string (which is a case of a {@link BigIntSafeType}), |
||
| 113 | * a {@link BigIntSafeStepper} is used; |
||
| 114 | * <li>otherwise, no discrete stepper is used (especially, if both bounds are |
||
| 115 | * <tt>null</tt>, no discrete stepper is actually useful) |
||
| 116 | * </ul> |
||
| 117 | * @return Range |
||
| 118 | */ |
||
| 119 | public static function fromBounds( |
||
| 158 | |||
| 159 | private static function inferDiscreteStepper($value): ?IDiscreteStepper |
||
| 179 | |||
| 180 | /** |
||
| 181 | * Creates a new empty range. |
||
| 182 | * |
||
| 183 | * @return Range |
||
| 184 | */ |
||
| 185 | public static function empty(): Range |
||
| 190 | |||
| 191 | private static function processBoundSpec($boundsOrLowerInc = '[)', ?bool $upperInc = null) |
||
| 215 | |||
| 216 | |||
| 217 | private function __construct( |
||
| 234 | |||
| 235 | //endregion |
||
| 236 | |||
| 237 | //region getters |
||
| 238 | |||
| 239 | final public function isEmpty(): bool |
||
| 243 | |||
| 244 | /** |
||
| 245 | * @return mixed lower bound, or <tt>null</tt> if the range is lower-unbounded or empty |
||
| 246 | */ |
||
| 247 | final public function getLower() |
||
| 251 | |||
| 252 | /** |
||
| 253 | * @return mixed upper bound, or <tt>null</tt> if the range is upper-unbounded or empty |
||
| 254 | */ |
||
| 255 | final public function getUpper() |
||
| 259 | |||
| 260 | /** |
||
| 261 | * @return bool|null whether the range includes its lower bound, or <tt>null</tt> if the range is empty; |
||
| 262 | * for lower-unbounded ranges, <tt>false</tt> is returned by definition |
||
| 263 | */ |
||
| 264 | final public function isLowerInc(): ?bool |
||
| 268 | |||
| 269 | /** |
||
| 270 | * @return bool|null whether the range includes its upper bound, or <tt>null</tt> if the range is empty; |
||
| 271 | * for upper-unbounded ranges, <tt>false</tt> is returned by definition |
||
| 272 | */ |
||
| 273 | final public function isUpperInc(): ?bool |
||
| 277 | |||
| 278 | /** |
||
| 279 | * @return string|null the bounds inclusive/exclusive specification, as accepted by {@link fromBounds()}, or |
||
| 280 | * <tt>null</tt> if the range is empty |
||
| 281 | */ |
||
| 282 | final public function getBoundsSpec(): ?string |
||
| 290 | |||
| 291 | /** |
||
| 292 | * @return bool whether the range is just a single point |
||
| 293 | */ |
||
| 294 | final public function isSinglePoint(): bool |
||
| 315 | |||
| 316 | /** |
||
| 317 | * Returns the range bounds according to the requested bound specification. |
||
| 318 | * |
||
| 319 | * **Only defined on ranges of {@link IDiscreteStepper discrete} subtypes.** |
||
| 320 | * |
||
| 321 | * E.g., on an integer range `(null,3)`, if bounds `[]` are requested, a pair of `null` and `2` is returned. |
||
| 322 | * |
||
| 323 | * @param bool|string $boundsOrLowerInc either the string of complete bounds specification, or a boolean telling |
||
| 324 | * whether the lower bound is inclusive; |
||
| 325 | * the complete specification is similar to PostgreSQL - it is a two-character |
||
| 326 | * string of <tt>'['</tt> or <tt>'('</tt>, and <tt>']'</tt> or <tt>')'</tt> |
||
| 327 | * (brackets denoting an inclusive bound, parentheses denoting an exclusive |
||
| 328 | * bound; |
||
| 329 | * when the boolean is used, also the <tt>$upperInc</tt> argument is used |
||
| 330 | * @param bool $upperInc whether the upper bound is inclusive; |
||
| 331 | * only relevant if <tt>$boundsOrLowerInc</tt> is also boolean |
||
| 332 | * @return array|null pair of the lower and upper bound, or <tt>null</tt> if the range is empty |
||
| 333 | * @throws UnsupportedException if the range subtype is not discrete and no custom discrete stepper has been |
||
| 334 | * provided |
||
| 335 | */ |
||
| 336 | public function toBounds($boundsOrLowerInc, ?bool $upperInc = null): ?array |
||
| 377 | |||
| 378 | final public function __toString() |
||
| 392 | |||
| 393 | //endregion |
||
| 394 | |||
| 395 | //region range operations |
||
| 396 | |||
| 397 | /** |
||
| 398 | * @param mixed $element a value of the range subtype |
||
| 399 | * @return bool|null whether this range contains the given element; |
||
| 400 | * <tt>null</tt> on <tt>null</tt> input |
||
| 401 | */ |
||
| 402 | public function containsElement($element): ?bool |
||
| 427 | |||
| 428 | /** |
||
| 429 | * @param mixed $element value of the range subtype |
||
| 430 | * @return bool|null <tt>true</tt> iff this range is left of the given element - value of the range subtype; |
||
| 431 | * <tt>false</tt> otherwise, especially if this range is empty; |
||
| 432 | * <tt>null</tt> on <tt>null</tt> input |
||
| 433 | */ |
||
| 434 | public function leftOfElement($element): ?bool |
||
| 449 | |||
| 450 | /** |
||
| 451 | * @param mixed $element value of the range subtype |
||
| 452 | * @return bool|null <tt>true</tt> iff this range is right of the given element - value of the range subtype; |
||
| 453 | * <tt>false</tt> otherwise, especially if this range is empty; |
||
| 454 | * <tt>null</tt> on <tt>null</tt> input |
||
| 455 | */ |
||
| 456 | public function rightOfElement($element): ?bool |
||
| 471 | |||
| 472 | /** |
||
| 473 | * @param Range $other a range of the same subtype as this range |
||
| 474 | * @return bool|null whether this range entirely contains the other range; |
||
| 475 | * an empty range is considered to be contained in any range, even an empty one; |
||
| 476 | * <tt>null</tt> on <tt>null</tt> input |
||
| 477 | */ |
||
| 478 | public function containsRange(?Range $other): ?bool |
||
| 514 | |||
| 515 | /** |
||
| 516 | * @param Range $other a range of the same subtype as this range |
||
| 517 | * @return bool|null whether this range is entirely contained in the other range; |
||
| 518 | * an empty range is considered to be contained in any range, even an empty one; |
||
| 519 | * <tt>null</tt> on <tt>null</tt> input |
||
| 520 | */ |
||
| 521 | public function containedInRange(?Range $other): ?bool |
||
| 528 | |||
| 529 | /** |
||
| 530 | * @param Range $other a range of the same subtype as this range |
||
| 531 | * @return bool|null whether this and the other range overlap, i.e., have a non-empty intersection; |
||
| 532 | * <tt>null</tt> on <tt>null</tt> input |
||
| 533 | */ |
||
| 534 | public function overlaps(?Range $other): ?bool |
||
| 557 | |||
| 558 | /** |
||
| 559 | * Computes the intersection of this range with another range. |
||
| 560 | * |
||
| 561 | * @param Range $other a range of the same subtype as this range |
||
| 562 | * @return Range|null intersection of this and the other range |
||
| 563 | * <tt>null</tt> on <tt>null</tt> input |
||
| 564 | */ |
||
| 565 | public function intersect(?Range $other): ?Range |
||
| 619 | |||
| 620 | /** |
||
| 621 | * @return bool whether the range is finite, i.e., neither starts nor ends in the infinity; |
||
| 622 | * note that an empty range is considered as finite |
||
| 623 | */ |
||
| 624 | final public function isFinite(): bool |
||
| 628 | |||
| 629 | /** |
||
| 630 | * @param Range|null $other a range of the same subtype as this range |
||
| 631 | * @return bool|null <tt>true</tt> iff this range is strictly left of the other range, i.e., it ends before the |
||
| 632 | * other starts; |
||
| 633 | * <tt>false</tt> otherwise, especially if either range is empty; |
||
| 634 | * <tt>null</tt> on <tt>null</tt> input |
||
| 635 | */ |
||
| 636 | public function strictlyLeftOf(?Range $other): ?bool |
||
| 651 | |||
| 652 | /** |
||
| 653 | * @param Range|null $other a range of the same subtype as this range |
||
| 654 | * @return bool|null <tt>true</tt> iff this range is strictly left of the other range, i.e., it ends before the |
||
| 655 | * other starts; |
||
| 656 | * <tt>false</tt> otherwise, especially if either range is empty;; |
||
| 657 | * <tt>null</tt> on <tt>null</tt> input |
||
| 658 | */ |
||
| 659 | public function strictlyRightOf(?Range $other): ?bool |
||
| 674 | |||
| 675 | //endregion |
||
| 676 | |||
| 677 | //region IComparable |
||
| 678 | |||
| 679 | public function equals($other): bool |
||
| 735 | |||
| 736 | public function compareTo($other): int |
||
| 760 | |||
| 761 | private function compareBounds(int $sgn, $aVal, bool $aIsInc, $bVal, bool $bIsInc): int |
||
| 786 | |||
| 787 | //endregion |
||
| 788 | |||
| 789 | //region ArrayAccess |
||
| 790 | |||
| 791 | public function offsetExists($offset) |
||
| 795 | |||
| 796 | public function offsetGet($offset) |
||
| 807 | |||
| 808 | public function offsetSet($offset, $value) |
||
| 812 | |||
| 813 | public function offsetUnset($offset) |
||
| 817 | |||
| 818 | //endregion |
||
| 819 | } |
||
| 820 |