From 0f39559ff9760d2561c788d46103d91cdba4fdba Mon Sep 17 00:00:00 2001 From: K Muller Date: Mon, 21 Sep 2026 14:24:37 +0200 Subject: [PATCH] feat: :sparkles: add VO and Enums to works on Coupon/CampaignRequest --- src/Domain/Enums/Country.php | 28 +++ src/Domain/Enums/Currency.php | 22 +++ .../Models/ValueObjects/MoneyAmount.php | 30 ++++ .../ValueObjects/RequestedCoupon/Period.php | 16 ++ src/Shared/Enums/BaseEnum.php | 160 ++++++++++++++++++ src/Shared/ValueObjects/DateTime.php | 51 ++++++ src/Shared/ValueObjects/Money.php | 42 +++++ 7 files changed, 349 insertions(+) create mode 100644 src/Domain/Enums/Country.php create mode 100644 src/Domain/Enums/Currency.php create mode 100644 src/Domain/Models/ValueObjects/MoneyAmount.php create mode 100644 src/Domain/Models/ValueObjects/RequestedCoupon/Period.php create mode 100644 src/Shared/Enums/BaseEnum.php create mode 100644 src/Shared/ValueObjects/DateTime.php create mode 100644 src/Shared/ValueObjects/Money.php diff --git a/src/Domain/Enums/Country.php b/src/Domain/Enums/Country.php new file mode 100644 index 0000000..a75b0e4 --- /dev/null +++ b/src/Domain/Enums/Country.php @@ -0,0 +1,28 @@ + */ + use BaseEnum; + + case FR = 1; + case BE = 2; + case LU = 3; + + /** + * Optional labels definition + * + * @phpstan-ignore-next-line + */ + private const LABELS = [ + self::FR->name => 'FR', + self::BE->name => 'BE', + self::LU->name => 'LU', + ]; +} diff --git a/src/Domain/Enums/Currency.php b/src/Domain/Enums/Currency.php new file mode 100644 index 0000000..ac68c2d --- /dev/null +++ b/src/Domain/Enums/Currency.php @@ -0,0 +1,22 @@ + */ + use BaseEnum; + + case EUR = 1; + + /** + * Optional labels definition + * + * @phpstan-ignore-next-line + */ + private const LABELS = [ + self::EUR->name => 'Euro', + ]; +} diff --git a/src/Domain/Models/ValueObjects/MoneyAmount.php b/src/Domain/Models/ValueObjects/MoneyAmount.php new file mode 100644 index 0000000..cb0b753 --- /dev/null +++ b/src/Domain/Models/ValueObjects/MoneyAmount.php @@ -0,0 +1,30 @@ +value->equals($other->value); + } + + public function __toString(): string + { + return (string) $this->value; + } +} diff --git a/src/Domain/Models/ValueObjects/RequestedCoupon/Period.php b/src/Domain/Models/ValueObjects/RequestedCoupon/Period.php new file mode 100644 index 0000000..9e75316 --- /dev/null +++ b/src/Domain/Models/ValueObjects/RequestedCoupon/Period.php @@ -0,0 +1,16 @@ +value; + } + + public function label(): string + { + return self::toLabels()[$this->name] ?? ucfirst(strtolower($this->name)); + } + + public function name(): string + { + return $this->name; + } + + /** + * @return array + */ + public static function toLabels(): array + { + static $cache = null; + + if ($cache !== null) { + return $cache; + } + + $labels = defined(self::class . '::' . self::LABELS_CONSTANT_NAME) + ? constant(self::class . '::' . self::LABELS_CONSTANT_NAME) + : null; + + $result = []; + foreach (self::cases() as $enum) { + if ($labels !== null && ! isset($labels[$enum->name])) { + throw new \Error('Missing definition label ' . self::class . " for '{$enum->name}'"); + } + $result[$enum->name] = $labels[$enum->name] ?? ucfirst(strtolower($enum->name)); + } + + return $cache = $result; + } + + /** + * @return array + */ + public static function toValues(): array + { + static $cache = null; + + if ($cache !== null) { + return $cache; + } + + return $cache = array_column( + self::cases(), + 'value', + 'name' + ); + } + + /** + * @param self|V $value + */ + public static function make(self | int | string $value, string $field = 'value'): ?self + { + if ($value instanceof self) { + return $value; + } + + if ($field === 'value') { + $tryValue = null; + try { + $tryValue = self::tryFrom(intval($value)); + } catch (\ValueError) { + $tryValue = self::tryFrom(strval($value)); + } finally { + if ($tryValue !== null) { + return $tryValue; + } + } + } + + if ($field === 'label') { + $normalizedSearch = preg_replace('/[\W_]+/u', '', mb_strtolower((string) $value)); + + foreach (self::cases() as $enum) { + $normalizedLabel = preg_replace('/[\W_]+/u', '', mb_strtolower($enum->label())); + if ($normalizedLabel === $normalizedSearch) { + return $enum; + } + } + + return null; + } + + foreach (self::cases() as $enum) { + if ($enum->{$field} === $value) { + return $enum; + } + } + + return null; + } + + public function equals(mixed $other): bool + { + return $this === $other; + } + + public function jsonSerialize(): mixed + { + return $this->value; + } + + // * PRIVATE + + /** + * @return array> + */ + private static function getCustomDefinitions(): array + { + static $cache = null; + + if ($cache !== null) { + return $cache; + } + + $names = array_column(self::cases(), 'name', 'name'); + $filteredConstants = []; + + foreach ((new \ReflectionClass(self::class))->getConstants() as $key => $constant) { + if ($key === self::LABELS_CONSTANT_NAME || ! \is_array($constant)) { + continue; + } + + if (! empty(array_intersect_key($constant, $names))) { + $filteredConstants[$key] = $constant; + } + } + + return $cache = $filteredConstants; + } +} diff --git a/src/Shared/ValueObjects/DateTime.php b/src/Shared/ValueObjects/DateTime.php new file mode 100644 index 0000000..d8007d1 --- /dev/null +++ b/src/Shared/ValueObjects/DateTime.php @@ -0,0 +1,51 @@ +value = \DateTimeImmutable::createFromInterface($dateTime) + ->setTimezone(new \DateTimeZone('UTC')); + } + + public static function now(): self + { + return new self(DatesHelper::now()); + } + + #[\Override] + public function equals(mixed $other): bool + { + return $other instanceof self + && $this->value->format(\DateTimeInterface::RFC3339_EXTENDED) === $other->value->format(\DateTimeInterface::RFC3339_EXTENDED); + } + + public function __toString(): string + { + return DatesHelper::toRfc3339($this->value); + } + + public function jsonSerialize(): string + { + return (string) $this; + } +} diff --git a/src/Shared/ValueObjects/Money.php b/src/Shared/ValueObjects/Money.php new file mode 100644 index 0000000..08529cb --- /dev/null +++ b/src/Shared/ValueObjects/Money.php @@ -0,0 +1,42 @@ +value < 0) { + throw new \DomainException('Money value cannot be negative.'); + } + } + + #[\Override] + public function equals(mixed $other): bool + { + return $other instanceof self + && $this->currency->equals($other->currency) + && $this->value === $other->value; + } + + public function __toString(): string + { + return sprintf('%d %s', $this->value, $this->currency->name()); + } +}