feat: ✨ add VO and Enums to works on Coupon/CampaignRequest
This commit is contained in:
parent
32a9a83b5b
commit
0f39559ff9
7 changed files with 349 additions and 0 deletions
28
src/Domain/Enums/Country.php
Normal file
28
src/Domain/Enums/Country.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Enums;
|
||||
|
||||
use Shared\Enums\BaseEnum;
|
||||
|
||||
enum Country: int
|
||||
{
|
||||
/** @use \Shared\Enums\BaseEnum<int> */
|
||||
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',
|
||||
];
|
||||
}
|
||||
22
src/Domain/Enums/Currency.php
Normal file
22
src/Domain/Enums/Currency.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shared\Enums;
|
||||
|
||||
enum Currency: int
|
||||
{
|
||||
/** @use BaseEnum<int> */
|
||||
use BaseEnum;
|
||||
|
||||
case EUR = 1;
|
||||
|
||||
/**
|
||||
* Optional labels definition
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
private const LABELS = [
|
||||
self::EUR->name => 'Euro',
|
||||
];
|
||||
}
|
||||
30
src/Domain/Models/ValueObjects/MoneyAmount.php
Normal file
30
src/Domain/Models/ValueObjects/MoneyAmount.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects;
|
||||
|
||||
use Shared\DDD\Models\Equatable;
|
||||
use Shared\ValueObjects\Money;
|
||||
|
||||
/**
|
||||
* Wraps a Money value as a requested/expected monetary amount.
|
||||
*/
|
||||
final readonly class MoneyAmount implements \Stringable, Equatable
|
||||
{
|
||||
public function __construct(
|
||||
public Money $value,
|
||||
) {
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function equals(mixed $other): bool
|
||||
{
|
||||
return $other instanceof self && $this->value->equals($other->value);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) $this->value;
|
||||
}
|
||||
}
|
||||
16
src/Domain/Models/ValueObjects/RequestedCoupon/Period.php
Normal file
16
src/Domain/Models/ValueObjects/RequestedCoupon/Period.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\RequestedCoupon;
|
||||
|
||||
use Shared\ValueObjects\DateTime;
|
||||
|
||||
final readonly class Period
|
||||
{
|
||||
public function __construct(
|
||||
public DateTime $start,
|
||||
public DateTime $end,
|
||||
) {
|
||||
}
|
||||
}
|
||||
160
src/Shared/Enums/BaseEnum.php
Normal file
160
src/Shared/Enums/BaseEnum.php
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shared\Enums;
|
||||
|
||||
/**
|
||||
* @template V of string|int
|
||||
*
|
||||
* @mixin \BackedEnum
|
||||
*/
|
||||
trait BaseEnum
|
||||
{
|
||||
private const LABELS_CONSTANT_NAME = 'LABELS';
|
||||
|
||||
/**
|
||||
* @return V
|
||||
*/
|
||||
public function value(): string | int
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return self::toLabels()[$this->name] ?? ucfirst(strtolower($this->name));
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,string>
|
||||
*/
|
||||
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<string, V>
|
||||
*/
|
||||
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<string, array<string, mixed>>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
51
src/Shared/ValueObjects/DateTime.php
Normal file
51
src/Shared/ValueObjects/DateTime.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shared\ValueObjects;
|
||||
|
||||
use Shared\DDD\Models\Equatable;
|
||||
use Shared\Helpers\DatesHelper;
|
||||
|
||||
/**
|
||||
* Represents a valid point in time, always normalized to UTC.
|
||||
*/
|
||||
final readonly class DateTime implements \Stringable, \JsonSerializable, Equatable
|
||||
{
|
||||
public \DateTimeImmutable $value;
|
||||
|
||||
/**
|
||||
* @param \DateTimeInterface|string $value A DateTimeInterface instance or a parsable date string.
|
||||
* @throws \DomainException
|
||||
*/
|
||||
public function __construct(
|
||||
\DateTimeInterface | string $value,
|
||||
) {
|
||||
$dateTime = is_string($value) ? DatesHelper::parseDateTime($value) : $value;
|
||||
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
42
src/Shared/ValueObjects/Money.php
Normal file
42
src/Shared/ValueObjects/Money.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shared\ValueObjects;
|
||||
|
||||
use Shared\DDD\Models\Equatable;
|
||||
use Shared\Enums\Currency;
|
||||
|
||||
/**
|
||||
* Represents a monetary amount, expressed as an integer in the currency's
|
||||
* smallest unit (e.g. cents).
|
||||
*/
|
||||
final readonly class Money implements \Stringable, Equatable
|
||||
{
|
||||
/**
|
||||
* @param Currency $currency Currency of the amount.
|
||||
* @param int $value Amount expressed in the currency's smallest unit (e.g. cents). Must be zero or positive.
|
||||
* @throws \DomainException
|
||||
*/
|
||||
public function __construct(
|
||||
public Currency $currency,
|
||||
public int $value,
|
||||
) {
|
||||
if ($this->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());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue