feat: ✨ create Tag entity and associated VOs
This commit is contained in:
parent
b6975d913a
commit
b5155c8ade
6 changed files with 255 additions and 0 deletions
60
src/Domain/Models/Entities/Tag.php
Normal file
60
src/Domain/Models/Entities/Tag.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\Entities;
|
||||
use Domain\Models\ValueObjects\Tag\TagId;
|
||||
use Domain\Models\ValueObjects\Tag\TagName;
|
||||
use Shared\DDD\Models\AggregateRoot;
|
||||
use Shared\DDD\Models\HasTimestampIdentifier;
|
||||
|
||||
/**
|
||||
* @implements \Shared\DDD\Models\AggregateRoot<\Domain\Models\ValueObjects\Tag\TagId>
|
||||
*/
|
||||
final readonly class Tag implements AggregateRoot, HasTimestampIdentifier
|
||||
{
|
||||
private function __construct(
|
||||
public TagId $id,
|
||||
public TagName $value,
|
||||
public ?\DateTimeInterface $updatedAt = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function id(): TagId
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function createdAt(): \DateTimeInterface
|
||||
{
|
||||
return $this->id->toDateTime();
|
||||
}
|
||||
|
||||
public function updatedAt(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public static function create(
|
||||
TagName $value,
|
||||
?\DateTimeInterface $updatedAt = null,
|
||||
): Tag {
|
||||
return new self(
|
||||
id: TagId::generate(),
|
||||
value: $value,
|
||||
updatedAt: $updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
public static function hydrate(
|
||||
TagId $id,
|
||||
TagName $value,
|
||||
?\DateTimeInterface $updatedAt = null,
|
||||
): self {
|
||||
return new self(
|
||||
id: $id,
|
||||
value: $value,
|
||||
updatedAt: $updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
16
src/Domain/Models/ValueObjects/Tag/TagId.php
Normal file
16
src/Domain/Models/ValueObjects/Tag/TagId.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Tag;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Shared\DDD\Models\Uuidv7EntityId;
|
||||
|
||||
final readonly class TagId extends Uuidv7EntityId
|
||||
{
|
||||
public static function generate(): self
|
||||
{
|
||||
return new self(Uuid::uuid7());
|
||||
}
|
||||
}
|
||||
18
src/Domain/Models/ValueObjects/Tag/TagName.php
Normal file
18
src/Domain/Models/ValueObjects/Tag/TagName.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Tag;
|
||||
|
||||
final readonly class TagName
|
||||
{
|
||||
public function __construct(
|
||||
public string $value,
|
||||
) {
|
||||
$trimmed = trim($value);
|
||||
|
||||
if ($trimmed === '') {
|
||||
throw new \DomainException('Tag name cannot be empty.');
|
||||
}
|
||||
}
|
||||
}
|
||||
103
tests/Unit/Domain/Models/Entities/TagTest.php
Normal file
103
tests/Unit/Domain/Models/Entities/TagTest.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Domain\Models\Entities;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Domain\Models\ValueObjects\Tag\TagId;
|
||||
use Domain\Models\ValueObjects\Tag\TagName;
|
||||
use Domain\Models\Entities\Tag;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
covers(Tag::class);
|
||||
|
||||
describe('tag', function (): void {
|
||||
afterEach(function (): void {
|
||||
CarbonImmutable::setTestNow();
|
||||
});
|
||||
|
||||
it('creates a tag', function (): void {
|
||||
// GIVEN
|
||||
$id = TagId::generate();
|
||||
$value = new TagName('Name');
|
||||
|
||||
// WHEN
|
||||
$tag = Tag::hydrate(
|
||||
$id,
|
||||
$value,
|
||||
);
|
||||
|
||||
// THEN
|
||||
expect($tag->id())->toBe($id)
|
||||
->and($tag->value)->toBe($value)
|
||||
->and($tag->updatedAt)->toBeNull()
|
||||
->and($tag->updatedAt())->toBeNull();
|
||||
});
|
||||
|
||||
it('creates a tag with an explicit updatedAt date', function (): void {
|
||||
// GIVEN
|
||||
$id = TagId::generate();
|
||||
$name = new TagName('Name');
|
||||
$updatedAt = new \DateTimeImmutable('2026-09-22 10:00:00', new \DateTimeZone('UTC'));
|
||||
|
||||
// WHEN
|
||||
$tag = Tag::hydrate(
|
||||
$id,
|
||||
$name,
|
||||
$updatedAt
|
||||
);
|
||||
|
||||
// THEN
|
||||
expect($tag->updatedAt)->toBe($updatedAt)
|
||||
->and($tag->updatedAt())->toBe($updatedAt);
|
||||
});
|
||||
|
||||
it('creates a new tag with a freshly generated id', function (): void {
|
||||
// GIVEN
|
||||
$value = new TagName('Name');
|
||||
|
||||
// WHEN
|
||||
$tag = Tag::create(
|
||||
$value,
|
||||
);
|
||||
|
||||
// THEN
|
||||
expect($tag->id())->toBeInstanceOf(TagId::class)
|
||||
->and($tag->value)->toBe($value)
|
||||
->and($tag->updatedAt)->toBeNull()
|
||||
->and($tag->updatedAt())->toBeNull();
|
||||
});
|
||||
|
||||
it('generates a different id for each created tag', function (): void {
|
||||
// GIVEN
|
||||
$value = new TagName('Name');
|
||||
|
||||
// WHEN
|
||||
$firstTag = Tag::create(
|
||||
$value
|
||||
);
|
||||
$secondTag = Tag::create(
|
||||
$value
|
||||
);
|
||||
|
||||
// THEN
|
||||
expect($firstTag->id()->equals($secondTag->id()))->toBeFalse();
|
||||
});
|
||||
|
||||
it('returns the creation date extracted from the id', function (): void {
|
||||
// GIVEN
|
||||
$expected = new \DateTimeImmutable('2024-06-15 12:34:56.789000', new \DateTimeZone('UTC'));
|
||||
$id = new TagId(Uuid::uuid7($expected));
|
||||
$tag = Tag::hydrate(
|
||||
$id,
|
||||
new TagName('Name'),
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$result = $tag->createdAt();
|
||||
|
||||
// THEN
|
||||
expect($result->format('Y-m-d H:i:s.v'))->toBe($expected->format('Y-m-d H:i:s.v'));
|
||||
});
|
||||
});
|
||||
28
tests/Unit/Domain/Models/ValueObjects/Tag/TagIdTest.php
Normal file
28
tests/Unit/Domain/Models/ValueObjects/Tag/TagIdTest.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Domain\Models\ValueObjects\Tag;
|
||||
|
||||
use Domain\Models\ValueObjects\Tag\TagId;
|
||||
|
||||
covers(TagId::class);
|
||||
|
||||
describe('brand id', function (): void {
|
||||
it('generates a unique tag id', function (): void {
|
||||
// WHEN
|
||||
$id = TagId::generate();
|
||||
|
||||
// THEN
|
||||
expect($id)->toBeInstanceOf(TagId::class);
|
||||
});
|
||||
|
||||
it('generates different ids on each call', function (): void {
|
||||
// WHEN
|
||||
$first = TagId::generate();
|
||||
$second = TagId::generate();
|
||||
|
||||
// THEN
|
||||
expect((string) $first)->not->toBe((string) $second);
|
||||
});
|
||||
});
|
||||
30
tests/Unit/Domain/Models/ValueObjects/Tag/TagNameTest.php
Normal file
30
tests/Unit/Domain/Models/ValueObjects/Tag/TagNameTest.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Domain\Models\ValueObjects\Tag;
|
||||
|
||||
use Domain\Models\ValueObjects\Tag\TagName;
|
||||
|
||||
covers(TagName::class);
|
||||
|
||||
describe('tag name', function (): void {
|
||||
it('creates a valid tag name', function (): void {
|
||||
// GIVEN
|
||||
$value = 'Name';
|
||||
|
||||
// WHEN
|
||||
$name = new TagName($value);
|
||||
|
||||
// THEN
|
||||
expect($name->value)->toBe($value);
|
||||
});
|
||||
|
||||
it('rejects invalid tag name', function (): void {
|
||||
// GIVEN
|
||||
$value = '';
|
||||
|
||||
// GIVEN / THEN
|
||||
expect(fn (): TagName => new TagName($value))->toThrow(\DomainException::class);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue