Complex classes like EndlessPeriod 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 EndlessPeriod, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
15 | class EndlessPeriod implements IteratorAggregate, PeriodInterface |
||
16 | { |
||
17 | /** @var \DateTimeImmutable */ |
||
18 | protected $start; |
||
19 | |||
20 | /** @var \DateTimeImmutable */ |
||
21 | protected $end; |
||
22 | |||
23 | /** @var \DateInterval */ |
||
24 | protected $interval; |
||
25 | |||
26 | /** @var \DateTimeImmutable */ |
||
27 | private $includedStart; |
||
28 | |||
29 | /** @var \DateTimeImmutable */ |
||
30 | private $includedEnd; |
||
31 | |||
32 | /** @var int */ |
||
33 | private $boundaryExclusionMask; |
||
34 | |||
35 | /** @var int */ |
||
36 | public $precisionMask; |
||
37 | |||
38 | public function __construct( |
||
39 | DateTimeImmutable $start, |
||
40 | ?DateTimeImmutable $end, |
||
41 | ?int $precisionMask = null, |
||
42 | ?int $boundaryExclusionMask = null |
||
43 | ) { |
||
44 | // if ($start > $end) { |
||
45 | // throw InvalidPeriod::endBeforeStart($start, $end); |
||
46 | // } |
||
47 | |||
48 | $this->boundaryExclusionMask = $boundaryExclusionMask ?? Boundaries::EXCLUDE_NONE; |
||
49 | $this->precisionMask = $precisionMask ?? Precision::DAY; |
||
50 | |||
51 | $this->start = $this->roundDate($start, $this->precisionMask); |
||
52 | $this->end = $end ? $this->roundDate($end, $this->precisionMask) : null; |
||
53 | $this->interval = $this->createDateInterval($this->precisionMask); |
||
54 | |||
55 | $this->includedStart = $this->startIncluded() |
||
56 | ? $this->start |
||
57 | : $this->start->add($this->interval); |
||
58 | |||
59 | $this->includedEnd = $this->endIncluded() |
||
60 | ? $this->end |
||
61 | : $this->end->sub($this->interval); |
||
62 | } |
||
63 | |||
64 | /** |
||
65 | * @param string|DateTimeInterface $start |
||
66 | * @param string|DateTimeInterface $end |
||
67 | * @param int|null $precisionMask |
||
68 | * @param int|null $boundaryExclusionMask |
||
69 | * @param string|null $format |
||
70 | * |
||
71 | * @return static |
||
72 | */ |
||
73 | public static function make( |
||
74 | $start, |
||
75 | $end = null, |
||
76 | ?int $precisionMask = null, |
||
77 | ?int $boundaryExclusionMask = null, |
||
78 | ?string $format = null |
||
79 | ): PeriodInterface { |
||
80 | if ($start === null) { |
||
81 | throw InvalidDate::cannotBeNull('Start date'); |
||
82 | } |
||
83 | |||
84 | // if ($end !== null) { |
||
85 | // throw InvalidDate::shouldBeNull('End date'); |
||
86 | // } |
||
87 | |||
88 | return new static( |
||
89 | static::resolveDate($start, $format), |
||
90 | $end ? static::resolveDate($end, $format) : null, |
||
91 | $precisionMask, |
||
92 | $boundaryExclusionMask |
||
93 | ); |
||
94 | } |
||
95 | |||
96 | public function startIncluded(): bool |
||
100 | |||
101 | public function startExcluded(): bool |
||
105 | |||
106 | public function endIncluded(): bool |
||
110 | |||
111 | public function endExcluded(): bool |
||
115 | |||
116 | public function getStart(): DateTimeImmutable |
||
120 | |||
121 | public function getIncludedStart(): DateTimeImmutable |
||
125 | |||
126 | public function getEnd(): DateTimeImmutable |
||
130 | |||
131 | public function getIncludedEnd(): ?DateTimeImmutable |
||
135 | |||
136 | public function length(): ?int |
||
140 | |||
141 | public function overlapsWith(PeriodInterface $period): bool |
||
155 | |||
156 | public function touchesWith(PeriodInterface $period): bool |
||
157 | { |
||
173 | |||
174 | public function startsBefore(DateTimeInterface $date): bool |
||
178 | |||
179 | public function startsBeforeOrAt(DateTimeInterface $date): bool |
||
183 | |||
184 | public function startsAfter(DateTimeInterface $date): bool |
||
188 | |||
189 | public function startsAfterOrAt(DateTimeInterface $date): bool |
||
193 | |||
194 | public function startsAt(DateTimeInterface $date): bool |
||
201 | |||
202 | public function endsBefore(DateTimeInterface $date): bool |
||
209 | |||
210 | public function endsBeforeOrAt(DateTimeInterface $date): bool |
||
217 | |||
218 | public function endsAfter(DateTimeInterface $date): bool |
||
225 | |||
226 | public function endsAfterOrAt(DateTimeInterface $date): bool |
||
233 | |||
234 | public function endsAt(DateTimeInterface $date): bool |
||
241 | |||
242 | public function contains(DateTimeInterface $date): bool |
||
254 | |||
255 | public function equals(PeriodInterface $period): bool |
||
269 | |||
270 | /** |
||
271 | * @param \Spatie\Period\Period $period |
||
272 | * |
||
273 | * @return static|null |
||
274 | * @throws \Exception |
||
275 | */ |
||
276 | public function gap(PeriodInterface $period): ?Period |
||
302 | |||
303 | /** |
||
304 | * @param \Spatie\Period\Period $period |
||
305 | * |
||
306 | * @return static|null |
||
307 | */ |
||
308 | public function overlapSingle(PeriodInterface $period): ?Period |
||
326 | |||
327 | /** |
||
328 | * @param \Spatie\Period\Period ...$periods |
||
329 | * |
||
330 | * @return \Spatie\Period\PeriodCollection|static[] |
||
331 | */ |
||
332 | public function overlap(PeriodInterface ...$periods): PeriodCollection |
||
348 | |||
349 | /** |
||
350 | * @param \Spatie\Period\Period ...$periods |
||
351 | * |
||
352 | * @return static |
||
353 | */ |
||
354 | public function overlapAll(PeriodInterface ...$periods): Period |
||
368 | |||
369 | public function diffSingle(PeriodInterface $period): PeriodCollection |
||
410 | |||
411 | /** |
||
412 | * @param \Spatie\Period\Period ...$periods |
||
413 | * |
||
414 | * @return \Spatie\Period\PeriodCollection|static[] |
||
415 | */ |
||
416 | public function diff(PeriodInterface ...$periods): PeriodCollection |
||
440 | |||
441 | public function getPrecisionMask(): int |
||
445 | |||
446 | public function getIterator() |
||
454 | |||
455 | protected static function resolveDate($date, ?string $format): DateTimeImmutable |
||
483 | |||
484 | protected static function resolveFormat($date, ?string $format): string |
||
496 | |||
497 | protected function roundDate(DateTimeInterface $date, int $precision): DateTimeImmutable |
||
512 | |||
513 | protected function createDateInterval(int $precision): DateInterval |
||
526 | |||
527 | protected function ensurePrecisionMatches(PeriodInterface $period): void |
||
535 | } |
||
536 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.