1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace EventSauce\EventSourcing; |
||
6 | |||
7 | use Generator; |
||
8 | use Throwable; |
||
9 | use function assert; |
||
10 | use function count; |
||
11 | |||
12 | /** |
||
13 | * @template T of AggregateRoot |
||
14 | * |
||
15 | * @template-implements AggregateRootRepository<T> |
||
16 | */ |
||
17 | class EventSourcedAggregateRootRepository implements AggregateRootRepository |
||
18 | { |
||
19 | /** @var class-string<T> */ |
||
20 | private string $aggregateRootClassName; |
||
21 | private MessageRepository $messages; |
||
22 | private MessageDecorator $decorator; |
||
23 | private MessageDispatcher $dispatcher; |
||
24 | |||
25 | /** |
||
26 | * @param class-string<T> $aggregateRootClassName |
||
0 ignored issues
–
show
Documentation
Bug
introduced
by
Loading history...
|
|||
27 | */ |
||
28 | public function __construct( |
||
29 | string $aggregateRootClassName, |
||
30 | MessageRepository $messageRepository, |
||
31 | MessageDispatcher $dispatcher = null, |
||
32 | MessageDecorator $decorator = null |
||
33 | ) { |
||
34 | $this->aggregateRootClassName = $aggregateRootClassName; |
||
35 | $this->messages = $messageRepository; |
||
36 | $this->dispatcher = $dispatcher ?: new SynchronousMessageDispatcher(); |
||
37 | $this->decorator = $decorator ?: new DefaultHeadersDecorator(); |
||
38 | } |
||
39 | |||
40 | public function retrieve(AggregateRootId $aggregateRootId): object |
||
41 | { |
||
42 | try { |
||
43 | /** @var AggregateRoot $className */ |
||
44 | /** @phpstan-var class-string<T> $className */ |
||
45 | $className = $this->aggregateRootClassName; |
||
46 | $events = $this->retrieveAllEvents($aggregateRootId); |
||
47 | |||
48 | return $className::reconstituteFromEvents($aggregateRootId, $events); |
||
49 | } catch (Throwable $throwable) { |
||
50 | throw UnableToReconstituteAggregateRoot::becauseOf($throwable->getMessage(), $throwable); |
||
51 | } |
||
52 | } |
||
53 | |||
54 | private function retrieveAllEvents(AggregateRootId $aggregateRootId): Generator |
||
55 | { |
||
56 | /** @var Generator<Message> $messages */ |
||
57 | $messages = $this->messages->retrieveAll($aggregateRootId); |
||
58 | |||
59 | foreach ($messages as $message) { |
||
60 | yield $message->event(); |
||
61 | } |
||
62 | |||
63 | return $messages->getReturn(); |
||
64 | } |
||
65 | |||
66 | public function persist(object $aggregateRoot): void |
||
67 | { |
||
68 | assert($aggregateRoot instanceof AggregateRoot, 'Expected $aggregateRoot to be an instance of ' . AggregateRoot::class); |
||
69 | |||
70 | $this->persistEvents( |
||
71 | $aggregateRoot->aggregateRootId(), |
||
72 | $aggregateRoot->aggregateRootVersion(), |
||
73 | ...$aggregateRoot->releaseEvents() |
||
74 | ); |
||
75 | } |
||
76 | |||
77 | public function persistEvents(AggregateRootId $aggregateRootId, int $aggregateRootVersion, object ...$events): void |
||
78 | { |
||
79 | if (0 === count($events)) { |
||
80 | return; |
||
81 | } |
||
82 | |||
83 | // decrease the aggregate root version by the number of raised events |
||
84 | // so the version of each message represents the version at the time |
||
85 | // of recording. |
||
86 | $aggregateRootVersion = $aggregateRootVersion - count($events); |
||
87 | $metadata = [Header::AGGREGATE_ROOT_ID => $aggregateRootId]; |
||
88 | $messages = array_map(fn (object $event) => $this->decorator->decorate(new Message( |
||
89 | $event, |
||
90 | $metadata + [Header::AGGREGATE_ROOT_VERSION => ++$aggregateRootVersion] |
||
91 | )), $events); |
||
92 | |||
93 | $this->messages->persist(...$messages); |
||
94 | $this->dispatcher->dispatch(...$messages); |
||
95 | } |
||
96 | } |
||
97 |