feat(shared): implement entity identity abstractions and base value objects
This commit is contained in:
parent
ba51a5c312
commit
c28ce7c488
10 changed files with 200 additions and 9 deletions
|
|
@ -4,6 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shared\DDD\Exceptions;
|
||||
|
||||
class NotFoundException extends \DomainException
|
||||
abstract class NotFoundException extends \DomainException
|
||||
{
|
||||
}
|
||||
|
|
|
|||
40
src/Shared/DDD/Models/Uuidv7EntityId.php
Normal file
40
src/Shared/DDD/Models/Uuidv7EntityId.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shared\DDD\Models;
|
||||
|
||||
use Shared\Helpers\UuidHelper;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
abstract readonly class Uuidv7EntityId extends EntityId
|
||||
{
|
||||
public function __construct(
|
||||
public UuidInterface $identifier
|
||||
) {
|
||||
parent::__construct($identifier->toString());
|
||||
|
||||
try {
|
||||
UuidHelper::validateUuid($identifier);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new \DomainException(
|
||||
"Invalid value '{$identifier->toString()}' is not a valid UUID",
|
||||
0,
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
if (! UuidHelper::isV7($identifier)) {
|
||||
throw new \DomainException(
|
||||
"Invalid value '{$identifier}' is not a valid V7 UUID",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function toDateTime(): \DateTimeInterface
|
||||
{
|
||||
return UuidHelper::toDateTime($this->identifier);
|
||||
}
|
||||
|
||||
abstract public static function generate(): static;
|
||||
}
|
||||
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shared\Models\ValueObjects;
|
||||
namespace Shared\ValueObjects;
|
||||
|
||||
use Shared\DDD\Models\Equatable;
|
||||
|
||||
/**
|
||||
* Represents a valid email.
|
||||
*/
|
||||
final readonly class Email
|
||||
final readonly class Email implements \Stringable, Equatable
|
||||
{
|
||||
public string $value;
|
||||
|
||||
|
|
@ -34,4 +36,15 @@ final readonly class Email
|
|||
|
||||
$this->value = mb_strtolower($trimmed, 'UTF-8');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function equals(mixed $other): bool
|
||||
{
|
||||
return $other instanceof self && $this->value === $other->value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shared\Models\ValueObjects;
|
||||
namespace Shared\ValueObjects;
|
||||
|
||||
use Shared\DDD\Models\Equatable;
|
||||
|
||||
/**
|
||||
* Represents a valid Rcs.
|
||||
* See: https://entreprendre.service-public.gouv.fr/vosdroits/F31190.
|
||||
*/
|
||||
final readonly class Rcs
|
||||
final readonly class Rcs implements \Stringable, Equatable
|
||||
{
|
||||
private const string REGEX = '/^RCS\s+[A-ZÀ-ÖØ-Ý][A-ZÀ-ÖØ-Ý\s-]*\s+\d{3}\s\d{3}\s\d{3}$/u';
|
||||
|
||||
|
|
@ -29,4 +31,15 @@ final readonly class Rcs
|
|||
throw new \DomainException('Rcs is not a valid Rcs text.');
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function equals(mixed $other): bool
|
||||
{
|
||||
return $other instanceof self && $this->value === $other->value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
11
tests/Unit/Shared/DDD/Models/DummyEntityId.php
Normal file
11
tests/Unit/Shared/DDD/Models/DummyEntityId.php
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Shared\DDD\Models;
|
||||
|
||||
use Shared\DDD\Models\EntityId;
|
||||
|
||||
final readonly class DummyEntityId extends EntityId
|
||||
{
|
||||
}
|
||||
16
tests/Unit/Shared/DDD/Models/DummyUuidv7EntityId.php
Normal file
16
tests/Unit/Shared/DDD/Models/DummyUuidv7EntityId.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Shared\DDD\Models;
|
||||
|
||||
use Shared\DDD\Models\Uuidv7EntityId;
|
||||
|
||||
final readonly class DummyUuidv7EntityId extends Uuidv7EntityId
|
||||
{
|
||||
#[\Override]
|
||||
public static function generate(): static
|
||||
{
|
||||
throw new \UnexpectedValueException('Not implemented');
|
||||
}
|
||||
}
|
||||
36
tests/Unit/Shared/DDD/Models/EntityIdTest.php
Normal file
36
tests/Unit/Shared/DDD/Models/EntityIdTest.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Shared\DDD\Models;
|
||||
|
||||
use Shared\DDD\Models\EntityId;
|
||||
|
||||
covers(EntityId::class);
|
||||
|
||||
describe('entity id', function (): void {
|
||||
it('should create an entity id', function (): void {
|
||||
// GIVEN / WHEN
|
||||
$id = new DummyEntityId('123');
|
||||
|
||||
// THEN
|
||||
expect($id->value)->toBe('123');
|
||||
});
|
||||
|
||||
it('can be casted to string', function (): void {
|
||||
// GIVEN / WHEN
|
||||
$id = new DummyEntityId('123');
|
||||
|
||||
// THEN
|
||||
expect((string) $id)->toBe('123');
|
||||
});
|
||||
|
||||
it('can be compared for equality', function (): void {
|
||||
// GIVEN / WHEN
|
||||
$id1 = new DummyEntityId('123');
|
||||
$id2 = new DummyEntityId('123');
|
||||
|
||||
// THEN
|
||||
expect($id1->equals($id2))->toBeTrue();
|
||||
});
|
||||
});
|
||||
62
tests/Unit/Shared/DDD/Models/Uuidv7EntityIdTest.php
Normal file
62
tests/Unit/Shared/DDD/Models/Uuidv7EntityIdTest.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Shared\DDD\Models;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Shared\DDD\Models\Uuidv7EntityId;
|
||||
|
||||
covers(Uuidv7EntityId::class);
|
||||
|
||||
describe('uuidv7 entity id', function (): void {
|
||||
it('should create a uuidv7 entity id', function (): void {
|
||||
// GIVEN / WHEN
|
||||
$uuid = Uuid::uuid7();
|
||||
$id = new DummyUuidv7EntityId($uuid);
|
||||
|
||||
// THEN
|
||||
expect($id->value)->toBe((string) $uuid);
|
||||
});
|
||||
|
||||
it('can be casted to string', function (): void {
|
||||
// GIVEN / WHEN
|
||||
$uuid = Uuid::uuid7();
|
||||
$id = new DummyUuidv7EntityId($uuid);
|
||||
|
||||
// THEN
|
||||
expect((string) $id)->toBe((string) $uuid);
|
||||
});
|
||||
|
||||
it('can be compared for equality', function (): void {
|
||||
// GIVEN / WHEN
|
||||
$uuid = Uuid::uuid7();
|
||||
$id1 = new DummyUuidv7EntityId($uuid);
|
||||
$id2 = new DummyUuidv7EntityId($uuid);
|
||||
|
||||
// THEN
|
||||
expect($id1->equals($id2))->toBeTrue();
|
||||
});
|
||||
|
||||
it('can be converted to a datetime', function (): void {
|
||||
// GIVEN / WHEN
|
||||
$uuid = Uuid::uuid7();
|
||||
$id = new DummyUuidv7EntityId($uuid);
|
||||
|
||||
// THEN
|
||||
expect($id->toDateTime())->toBeInstanceOf(\DateTimeImmutable::class);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uuidv7 entity id validation rules', function (): void {
|
||||
it('should throw an exception when non v7 UUID is used', function (): void {
|
||||
// GIVEN
|
||||
$uuid = Uuid::uuid4();
|
||||
|
||||
// THEN
|
||||
$this->expectException(\DomainException::class);
|
||||
|
||||
// WHEN
|
||||
new DummyUuidv7EntityId($uuid);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Shared\Models\ValueObjects;
|
||||
namespace Tests\Unit\Shared\ValueObjects;
|
||||
|
||||
use Shared\Models\ValueObjects\Email;
|
||||
use Shared\ValueObjects\Email;
|
||||
|
||||
covers(Email::class);
|
||||
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Shared\Models\ValueObjects;
|
||||
namespace Tests\Unit\Shared\ValueObjects;
|
||||
|
||||
use Shared\Models\ValueObjects\Rcs;
|
||||
use Shared\ValueObjects\Rcs;
|
||||
|
||||
covers(Rcs::class);
|
||||
|
||||
Loading…
Add table
Reference in a new issue