From 7b551547b647f86028e2ce0fe01c759b20ee012e Mon Sep 17 00:00:00 2001
From: Daniel Siepmann <daniel.siepmann@codappix.com>
Date: Tue, 11 Feb 2025 14:14:36 +0100
Subject: [PATCH] Refactor date factory

Use a dedicated object to represent the incoming data.
That way the factory can ask the data/date questions and fetch data.
We do not need to expose the internals to the factory.
---
 Classes/Domain/DestinationData/Date.php       | 117 ++++++++++++++++++
 Classes/Domain/Model/Date.php                 |   8 +-
 .../DatesFactory.php                          |  95 ++++----------
 3 files changed, 148 insertions(+), 72 deletions(-)
 create mode 100644 Classes/Domain/DestinationData/Date.php

diff --git a/Classes/Domain/DestinationData/Date.php b/Classes/Domain/DestinationData/Date.php
new file mode 100644
index 0000000..ad38d58
--- /dev/null
+++ b/Classes/Domain/DestinationData/Date.php
@@ -0,0 +1,117 @@
+<?php
+
+declare(strict_types=1);
+
+namespace WerkraumMedia\Events\Domain\DestinationData;
+
+use DateTimeImmutable;
+use DateTimeZone;
+use RuntimeException;
+
+final class Date
+{
+    public function __construct(
+        private readonly array $data
+    ) {
+    }
+
+    public function isSingle(): bool
+    {
+        $frequency = $this->data['freq'] ?? '';
+        $start = $this->data['start'] ?? '';
+
+        return $frequency === ''
+            && $start !== '';
+    }
+
+    public function isInterval(): bool
+    {
+        return $this->isDaily()
+            || $this->isWeekly();
+    }
+
+    public function isDaily(): bool
+    {
+        return $this->getFrequency() == 'Daily' && empty($this->data['weekdays']);
+    }
+
+    public function isWeekly(): bool
+    {
+        return $this->getFrequency() == 'Weekly' && !empty($this->data['weekdays']);
+    }
+
+    public function isAfter(DateTimeImmutable $comparison): bool
+    {
+        return new DateTimeImmutable($this->data['start']) > $comparison;
+    }
+
+    public function hasKnownRepeat(): bool
+    {
+        return empty($this->data['repeatUntil']) === false
+            || empty($this->data['repeatCount']) === false;
+    }
+
+    public function withRepeatUntil(string $repeatUntil): self
+    {
+        $data = $this->data;
+        $data['repeatUntil'] = $repeatUntil;
+
+        return new self($data);
+    }
+
+    public function getStart(): DateTimeImmutable
+    {
+        return new DateTimeImmutable(
+            $this->data['start'],
+            new DateTimeZone($this->data['tz'])
+        );
+    }
+
+    public function getEnd(): DateTimeImmutable
+    {
+        return new DateTimeImmutable(
+            $this->data['end'],
+            new DateTimeZone($this->data['tz'])
+        );
+    }
+
+    public function getTimeZone(): DateTimeZone
+    {
+        return new DateTimeZone($this->data['tz']);
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getWeekdays(): array
+    {
+        return $this->data['weekdays'];
+    }
+
+    public function getRepeatUntil(): DateTimeImmutable
+    {
+        if (array_key_exists('repeatUntil', $this->data)) {
+            return new DateTimeImmutable($this->data['repeatUntil'], $this->getTimezone());
+        }
+
+        $repeatCountUnit = '';
+        if ($this->isDaily()) {
+            $repeatCountUnit = 'days';
+        } elseif ($this->isWeekly()) {
+            $repeatCountUnit = 'weeks';
+        }
+
+        if ($repeatCountUnit === '') {
+            throw new RuntimeException('The current date can not repeat.', 1739279561);
+        }
+
+        return $this->getStart()->modify(
+            '+' . ((int)$this->data['repeatCount']) . ' ' . $repeatCountUnit
+        );
+    }
+
+    private function getFrequency(): string
+    {
+        return $this->data['freq'] ?? '';
+    }
+}
diff --git a/Classes/Domain/Model/Date.php b/Classes/Domain/Model/Date.php
index 4be5ecc..fb5e280 100644
--- a/Classes/Domain/Model/Date.php
+++ b/Classes/Domain/Model/Date.php
@@ -6,8 +6,8 @@ namespace WerkraumMedia\Events\Domain\Model;
 
 use DateTime;
 use DateTimeImmutable;
-use DateTimeZone;
 use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
+use WerkraumMedia\Events\Domain\DestinationData\Date as DestinationDataDate;
 
 /**
  * Date
@@ -126,12 +126,12 @@ class Date extends AbstractEntity
     }
 
     public static function createFromDestinationDataDate(
-        array $date,
+        DestinationDataDate $date,
         bool $canceled
     ): self {
         return self::createFromDestinationData(
-            new DateTimeImmutable($date['start'], new DateTimeZone($date['tz'])),
-            new DateTimeImmutable($date['end'], new DateTimeZone($date['tz'])),
+            $date->getStart(),
+            $date->getEnd(),
             $canceled
         );
     }
diff --git a/Classes/Service/DestinationDataImportService/DatesFactory.php b/Classes/Service/DestinationDataImportService/DatesFactory.php
index e0aeda3..879de14 100644
--- a/Classes/Service/DestinationDataImportService/DatesFactory.php
+++ b/Classes/Service/DestinationDataImportService/DatesFactory.php
@@ -7,11 +7,11 @@ namespace WerkraumMedia\Events\Service\DestinationDataImportService;
 use DateInterval;
 use DatePeriod;
 use DateTimeImmutable;
-use DateTimeZone;
 use Generator;
 use Psr\Log\LoggerInterface;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Log\LogManager;
+use WerkraumMedia\Events\Domain\DestinationData\Date as DestinationDataDate;
 use WerkraumMedia\Events\Domain\Model\Date;
 use WerkraumMedia\Events\Domain\Model\Import;
 
@@ -54,12 +54,14 @@ final class DatesFactory
         array $date,
         bool $canceled
     ): ?Generator {
-        if ($this->isDateSingleDate($date)) {
+        $date = new DestinationDataDate($date);
+
+        if ($date->isSingle()) {
             $this->logger->info('Is single date', ['date' => $date]);
             return $this->createSingleDate($date, $canceled);
         }
 
-        if ($this->isDateInterval($date)) {
+        if ($date->isInterval()) {
             $this->logger->info('Is interval date', ['date' => $date]);
             return $this->createDateFromInterval($import, $date, $canceled);
         }
@@ -67,38 +69,14 @@ final class DatesFactory
         return null;
     }
 
-    private function isDateSingleDate(array $date): bool
-    {
-        $frequency = $date['freq'] ?? '';
-        $start = $date['start'] ?? '';
-
-        return $frequency === ''
-            && $start !== '';
-    }
-
-    private function isDateInterval(array $date): bool
-    {
-        $frequency = $date['freq'] ?? '';
-
-        if ($frequency == 'Daily' && empty($date['weekdays'])) {
-            return true;
-        }
-
-        if ($frequency == 'Weekly' && !empty($date['weekdays'])) {
-            return true;
-        }
-
-        return false;
-    }
-
     /**
      * @return Generator<Date>
      */
     private function createSingleDate(
-        array $date,
+        DestinationDataDate $date,
         bool $canceled
     ): Generator {
-        if (new DateTimeImmutable($date['start']) > $this->getToday()) {
+        if ($date->isAfter($this->getToday())) {
             yield Date::createFromDestinationDataDate($date, $canceled);
         }
     }
@@ -108,16 +86,16 @@ final class DatesFactory
      */
     private function createDateFromInterval(
         Import $import,
-        array $date,
+        DestinationDataDate $date,
         bool $canceled
     ): ?Generator {
         $date = $this->ensureRepeatUntil($import, $date);
 
-        if ($date['freq'] == 'Daily') {
+        if ($date->isDaily()) {
             return $this->createDailyDates($date, $canceled);
         }
 
-        if ($date['freq'] == 'Weekly') {
+        if ($date->isWeekly()) {
             return $this->createWeeklyDates($date, $canceled);
         }
 
@@ -126,35 +104,31 @@ final class DatesFactory
 
     private function ensureRepeatUntil(
         Import $import,
-        array $date
-    ): array {
-        if (empty($date['repeatUntil']) === false) {
-            return $date;
-        }
-        if (empty($date['repeatCount']) === false) {
+        DestinationDataDate $date
+    ): DestinationDataDate {
+        if ($date->hasKnownRepeat()) {
             return $date;
         }
 
-        $date['repeatUntil'] = $this->getToday()->modify($import->getRepeatUntil())->format('c');
-        $this->logger->info('Interval did not provide repeatUntil.', ['newRepeat' => $date['repeatUntil']]);
+        $repeatUntil = $this->getToday()->modify($import->getRepeatUntil())->format('c');
+        $this->logger->info('Interval did not provide repeatUntil.', ['newRepeat' => $repeatUntil]);
 
-        return $date;
+        return $date->withRepeatUntil($repeatUntil);
     }
 
     /**
      * @return Generator<Date>
      */
     private function createDailyDates(
-        array $date,
+        DestinationDataDate $date,
         bool $canceled
     ): Generator {
         $today = $this->getToday();
-        $timeZone = new DateTimeZone($date['tz']);
-        $start = new DateTimeImmutable($date['start'], $timeZone);
-        $end = new DateTimeImmutable($date['end'], $timeZone);
-        $until = $this->createUntil($start, $date, 'days');
+        $timeZone = $date->getTimeZone();
+        $start = $date->getStart();
+        $end = $date->getEnd();
 
-        $period = new DatePeriod($start, new DateInterval('P1D'), $until);
+        $period = new DatePeriod($start, new DateInterval('P1D'), $date->getRepeatUntil());
         foreach ($period as $day) {
             $day = $day->setTimezone($timeZone);
             if ($day < $today) {
@@ -175,16 +149,16 @@ final class DatesFactory
      * @return Generator<Date>
      */
     private function createWeeklyDates(
-        array $date,
+        DestinationDataDate $date,
         bool $canceled
     ): Generator {
         $today = $this->getToday();
-        $timeZone = new DateTimeZone($date['tz']);
-        $start = new DateTimeImmutable($date['start'], $timeZone);
-        $end = new DateTimeImmutable($date['end'], $timeZone);
-        $until = $this->createUntil($start, $date, 'weeks');
+        $timeZone = $date->getTimeZone();
+        $start = $date->getStart();
+        $end = $date->getEnd();
+        $until = $date->getRepeatUntil();
 
-        foreach ($date['weekdays'] as $day) {
+        foreach ($date->getWeekdays() as $day) {
             $dateToUse = $start->modify($day);
             $dateToUse = $dateToUse->setTime((int)$start->format('H'), (int)$start->format('i'));
 
@@ -219,21 +193,6 @@ final class DatesFactory
         );
     }
 
-    /**
-     * @param string $repeatCountUnit E.g. weeks or days
-     */
-    private function createUntil(
-        DateTimeImmutable $start,
-        array $date,
-        string $repeatCountUnit,
-    ): DateTimeImmutable {
-        if (array_key_exists('repeatUntil', $date)) {
-            return new DateTimeImmutable($date['repeatUntil'], $start->getTimezone());
-        }
-
-        return $start->modify('+' . ((int)$date['repeatCount']) . ' ' . $repeatCountUnit);
-    }
-
     private function getToday(): DateTimeImmutable
     {
         $today = $this->context->getPropertyFromAspect('date', 'full', new DateTimeImmutable());