Skip to content

Commit d0f7440

Browse files
authored
Add AggregateIdFactory to construct AggregateIds for AggregateTypes (#571)
1 parent c2c23ce commit d0f7440

4 files changed

Lines changed: 230 additions & 0 deletions

File tree

src/AggregateIdFactory.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Lendable\Aggregate;
6+
7+
use Ramsey\Uuid\UuidInterface;
8+
9+
final class AggregateIdFactory
10+
{
11+
/**
12+
* @param non-empty-array<non-empty-string, class-string<AggregateId>> $map
13+
*
14+
* @throws \InvalidArgumentException If the map is empty.
15+
*/
16+
public function __construct(private readonly array $map)
17+
{
18+
if ($map === []) {
19+
throw new \InvalidArgumentException('Map cannot be empty.');
20+
}
21+
}
22+
23+
/**
24+
* @throws AggregateIdImplementationNotKnown If the aggregate type is not mapped.
25+
*/
26+
public function fromUuid(AggregateType $aggregateType, UuidInterface $value): AggregateId
27+
{
28+
return $this->aggregateIdClass($aggregateType)::fromUuid($value);
29+
}
30+
31+
/**
32+
* @param non-empty-string $value
33+
*
34+
* @throws AggregateIdImplementationNotKnown If the aggregate type is not mapped.
35+
*/
36+
public function fromBinary(AggregateType $aggregateType, string $value): AggregateId
37+
{
38+
return $this->aggregateIdClass($aggregateType)::fromBinary($value);
39+
}
40+
41+
/**
42+
* @param non-empty-string $value
43+
*
44+
* @throws AggregateIdImplementationNotKnown If the aggregate type is not mapped.
45+
*/
46+
public function fromString(AggregateType $aggregateType, string $value): AggregateId
47+
{
48+
return $this->aggregateIdClass($aggregateType)::fromString($value);
49+
}
50+
51+
/**
52+
* @return class-string<AggregateId>
53+
*
54+
* @throws AggregateIdImplementationNotKnown
55+
*/
56+
private function aggregateIdClass(AggregateType $aggregateType): string
57+
{
58+
return $this->map[$aggregateType->toString()]
59+
?? throw AggregateIdImplementationNotKnown::forAggregateType($aggregateType);
60+
}
61+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Lendable\Aggregate;
6+
7+
final class AggregateIdImplementationNotKnown extends \InvalidArgumentException
8+
{
9+
private function __construct(string $message)
10+
{
11+
parent::__construct($message);
12+
}
13+
14+
public static function forAggregateType(AggregateType $aggregateType): self
15+
{
16+
return new self(
17+
\sprintf('Aggregate id implementation is not known for aggregate type "%s".', $aggregateType->toString())
18+
);
19+
}
20+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\Lendable\Aggregate;
6+
7+
use Lendable\Aggregate\AggregateIdFactory;
8+
use Lendable\Aggregate\AggregateType;
9+
use Lendable\Aggregate\UuidV4AggregateId;
10+
use Lendable\Aggregate\UuidV7AggregateId;
11+
use PHPUnit\Framework\Attributes\CoversClass;
12+
use PHPUnit\Framework\Attributes\Test;
13+
use PHPUnit\Framework\TestCase;
14+
use Ramsey\Uuid\Uuid;
15+
16+
#[CoversClass(AggregateIdFactory::class)]
17+
final class AggregateIdFactoryTest extends TestCase
18+
{
19+
#[Test]
20+
public function creates_from_binary(): void
21+
{
22+
$factory = new AggregateIdFactory(['foo' => UuidV4AggregateId::class, 'bar' => UuidV7AggregateId::class]);
23+
24+
$fooUuid = Uuid::uuid4();
25+
$fooId = $factory->fromBinary(
26+
AggregateType::fromString('foo'),
27+
$fooUuid->getBytes()
28+
);
29+
30+
$this->assertInstanceOf(UuidV4AggregateId::class, $fooId);
31+
$this->assertSame($fooUuid->toString(), $fooId->toString());
32+
33+
$barUuid = Uuid::uuid7();
34+
$barId = $factory->fromBinary(
35+
AggregateType::fromString('bar'),
36+
$barUuid->getBytes()
37+
);
38+
39+
$this->assertInstanceOf(UuidV7AggregateId::class, $barId);
40+
$this->assertSame($barUuid->toString(), $barId->toString());
41+
}
42+
43+
#[Test]
44+
public function creates_from_string(): void
45+
{
46+
$factory = new AggregateIdFactory(['foo' => UuidV4AggregateId::class, 'bar' => UuidV7AggregateId::class]);
47+
48+
$fooUuid = Uuid::uuid4();
49+
$fooId = $factory->fromString(
50+
AggregateType::fromString('foo'),
51+
$fooUuid->toString()
52+
);
53+
54+
$this->assertInstanceOf(UuidV4AggregateId::class, $fooId);
55+
$this->assertSame($fooUuid->toString(), $fooId->toString());
56+
57+
$barUuid = Uuid::uuid7();
58+
$barId = $factory->fromString(
59+
AggregateType::fromString('bar'),
60+
$barUuid->toString()
61+
);
62+
63+
$this->assertInstanceOf(UuidV7AggregateId::class, $barId);
64+
$this->assertSame($barUuid->toString(), $barId->toString());
65+
}
66+
67+
#[Test]
68+
public function creates_from_uuid(): void
69+
{
70+
$factory = new AggregateIdFactory(['foo' => UuidV4AggregateId::class, 'bar' => UuidV7AggregateId::class]);
71+
72+
$fooUuid = Uuid::uuid4();
73+
$fooId = $factory->fromUuid(
74+
AggregateType::fromString('foo'),
75+
$fooUuid
76+
);
77+
78+
$this->assertSame($fooUuid->toString(), $fooId->toString());
79+
80+
$barUuid = Uuid::uuid7();
81+
$barId = $factory->fromUuid(
82+
AggregateType::fromString('bar'),
83+
$barUuid
84+
);
85+
86+
$this->assertSame($barUuid->toString(), $barId->toString());
87+
}
88+
89+
#[Test]
90+
public function throws_when_creating_from_binary_if_not_mapped(): void
91+
{
92+
$factory = new AggregateIdFactory(['foo' => UuidV4AggregateId::class]);
93+
94+
$this->expectExceptionObject(new \InvalidArgumentException('Aggregate id implementation is not known for aggregate type "bar".'));
95+
96+
$factory->fromBinary(AggregateType::fromString('bar'), Uuid::uuid4()->getBytes());
97+
}
98+
99+
#[Test]
100+
public function throws_when_creating_from_string_if_not_mapped(): void
101+
{
102+
$factory = new AggregateIdFactory(['foo' => UuidV4AggregateId::class]);
103+
104+
$this->expectExceptionObject(new \InvalidArgumentException('Aggregate id implementation is not known for aggregate type "bar".'));
105+
106+
$factory->fromString(AggregateType::fromString('bar'), Uuid::uuid4()->toString());
107+
}
108+
109+
#[Test]
110+
public function throws_when_creating_from_uuid_if_not_mapped(): void
111+
{
112+
$factory = new AggregateIdFactory(['foo' => UuidV4AggregateId::class]);
113+
114+
$this->expectExceptionObject(new \InvalidArgumentException('Aggregate id implementation is not known for aggregate type "bar".'));
115+
116+
$factory->fromUuid(AggregateType::fromString('bar'), Uuid::uuid4());
117+
}
118+
119+
#[Test]
120+
public function throws_if_constructed_with_empty_map(): void
121+
{
122+
$this->expectExceptionObject(new \InvalidArgumentException('Map cannot be empty.'));
123+
124+
new AggregateIdFactory([]); // @phpstan-ignore-line
125+
}
126+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\Lendable\Aggregate;
6+
7+
use PHPUnit\Framework\Attributes\Test;
8+
use Lendable\Aggregate\AggregateIdImplementationNotKnown;
9+
use Lendable\Aggregate\AggregateType;
10+
use PHPUnit\Framework\Attributes\CoversClass;
11+
use PHPUnit\Framework\TestCase;
12+
13+
#[CoversClass(AggregateIdImplementationNotKnown::class)]
14+
final class AggregateIdImplementationNotKnownTest extends TestCase
15+
{
16+
#[Test]
17+
public function constructs_with_a_formatted_message_from_input(): void
18+
{
19+
$exception = AggregateIdImplementationNotKnown::forAggregateType(AggregateType::fromString('foo'));
20+
21+
$this->assertSame('Aggregate id implementation is not known for aggregate type "foo".', $exception->getMessage());
22+
}
23+
}

0 commit comments

Comments
 (0)