Passed
Push — master ( 0315ad...16334a )
by Frank
37s queued 10s
created

src/ConstructingAggregateRootRepository.php (1 issue)

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