Completed
Push — master ( 00f04e...1d93d6 )
by Freek
03:46
created

Projectionist::addEventHandler()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 9.1928
c 0
b 0
f 0
cc 5
nc 8
nop 1
1
<?php
2
3
namespace Spatie\EventSourcing;
4
5
use Exception;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Collection;
8
use Spatie\EventSourcing\EventHandlers\EventHandler;
9
use Spatie\EventSourcing\EventHandlers\EventHandlerCollection;
10
use Spatie\EventSourcing\Events\EventHandlerFailedHandlingEvent;
11
use Spatie\EventSourcing\Events\FinishedEventReplay;
12
use Spatie\EventSourcing\Events\StartingEventReplay;
13
use Spatie\EventSourcing\Exceptions\InvalidEventHandler;
14
use Spatie\EventSourcing\Projectors\Projector;
15
use Spatie\EventSourcing\Projectors\QueuedProjector;
16
17
final class Projectionist
18
{
19
    private EventHandlerCollection $projectors;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
20
21
    private EventHandlerCollection $reactors;
22
23
    private bool $catchExceptions;
24
25
    private bool $replayChunkSize;
26
27
    private bool $isProjecting = false;
28
29
    private bool $isReplaying = false;
30
31
    public function __construct(array $config)
32
    {
33
        $this->projectors = new EventHandlerCollection();
34
        $this->reactors = new EventHandlerCollection();
35
36
        $this->catchExceptions = $config['catch_exceptions'];
37
        $this->replayChunkSize = $config['replay_chunk_size'];
38
    }
39
40
    public function addProjector($projector): Projectionist
41
    {
42
        if (is_string($projector)) {
43
            $projector = app($projector);
44
        }
45
46
        if (! $projector instanceof Projector) {
47
            throw InvalidEventHandler::notAProjector($projector);
48
        }
49
50
        $this->projectors->add($projector);
51
52
        return $this;
53
    }
54
55
    public function withoutEventHandlers(array $eventHandlers = null): Projectionist
56
    {
57
        if (is_null($eventHandlers)) {
58
            $this->projectors = new EventHandlerCollection();
59
            $this->reactors = new EventHandlerCollection();
60
61
            return $this;
62
        }
63
64
        $eventHandlers = Arr::wrap($eventHandlers);
65
66
        $this->projectors->remove($eventHandlers);
67
68
        $this->reactors->remove($eventHandlers);
69
70
        return $this;
71
    }
72
73
    public function withoutEventHandler(string $eventHandler): Projectionist
74
    {
75
        return $this->withoutEventHandlers([$eventHandler]);
76
    }
77
78
    public function addProjectors(array $projectors): Projectionist
79
    {
80
        foreach ($projectors as $projector) {
81
            $this->addProjector($projector);
82
        }
83
84
        return $this;
85
    }
86
87
    public function getProjectors(): Collection
88
    {
89
        return $this->projectors->all();
90
    }
91
92
    public function getProjector(string $name): ?Projector
93
    {
94
        return $this->projectors->all()->first(fn(Projector $projector) => $projector->getName() === $name);
95
    }
96
97
    public function addReactor($reactor): Projectionist
98
    {
99
        if (is_string($reactor)) {
100
            $reactor = app($reactor);
101
        }
102
103
        if (! $reactor instanceof EventHandler) {
104
            throw InvalidEventHandler::notAnEventHandler($reactor);
105
        }
106
107
        $this->reactors->add($reactor);
108
109
        return $this;
110
    }
111
112
    public function addReactors(array $reactors): Projectionist
113
    {
114
        foreach ($reactors as $reactor) {
115
            $this->addReactor($reactor);
116
        }
117
118
        return $this;
119
    }
120
121
    public function getReactors(): Collection
122
    {
123
        return $this->reactors->all();
124
    }
125
126
    public function addEventHandler($eventHandlerClass)
127
    {
128
        if (! is_string($eventHandlerClass)) {
129
            $eventHandlerClass = get_class($eventHandlerClass);
130
        }
131
132
        if (is_subclass_of($eventHandlerClass, Projector::class)) {
133
            $this->addProjector($eventHandlerClass);
134
135
            return;
136
        }
137
138
        if (is_subclass_of($eventHandlerClass, QueuedProjector::class)) {
139
            $this->addProjector($eventHandlerClass);
140
141
            return;
142
        }
143
144
        if (is_subclass_of($eventHandlerClass, EventHandler::class)) {
145
            $this->addReactor($eventHandlerClass);
146
147
            return;
148
        }
149
150
        throw InvalidEventHandler::notAnEventHandlingClassName($eventHandlerClass);
151
    }
152
153
    public function addEventHandlers(array $eventHandlers)
154
    {
155
        foreach ($eventHandlers as $eventHandler) {
156
            $this->addEventHandler($eventHandler);
157
        }
158
    }
159
160
    public function handle(StoredEvent $storedEvent): void
161
    {
162
        $projectors = $this->projectors
163
            ->forEvent($storedEvent)
164
            ->reject(fn(Projector $projector) => $projector->shouldBeCalledImmediately());
165
166
        $this->applyStoredEventToProjectors(
167
            $storedEvent,
168
            $projectors
169
        );
170
171
        $this->applyStoredEventToReactors(
172
            $storedEvent,
173
            $this->reactors->forEvent($storedEvent)
174
        );
175
    }
176
177
    public function handleWithSyncProjectors(StoredEvent $storedEvent): void
178
    {
179
        $projectors = $this->projectors
180
            ->forEvent($storedEvent)
181
            ->filter(fn(Projector $projector) => $projector->shouldBeCalledImmediately());
182
183
        $this->applyStoredEventToProjectors($storedEvent, $projectors);
184
    }
185
186
    public function isProjecting(): bool
187
    {
188
        return $this->isProjecting;
189
    }
190
191
    private function applyStoredEventToProjectors(StoredEvent $storedEvent, Collection $projectors): void
192
    {
193
        $this->isProjecting = true;
194
195
        foreach ($projectors as $projector) {
196
            $this->callEventHandler($projector, $storedEvent);
197
        }
198
199
        $this->isProjecting = false;
200
    }
201
202
    private function applyStoredEventToReactors(StoredEvent $storedEvent, Collection $reactors): void
203
    {
204
        foreach ($reactors as $reactor) {
205
            $this->callEventHandler($reactor, $storedEvent);
206
        }
207
    }
208
209
    private function callEventHandler(EventHandler $eventHandler, StoredEvent $storedEvent): bool
210
    {
211
        try {
212
            $eventHandler->handle($storedEvent);
213
        } catch (Exception $exception) {
214
            if (! $this->catchExceptions) {
215
                throw $exception;
216
            }
217
218
            $eventHandler->handleException($exception);
219
220
            event(new EventHandlerFailedHandlingEvent($eventHandler, $storedEvent, $exception));
221
222
            return false;
223
        }
224
225
        return true;
226
    }
227
228
    public function isReplaying(): bool
229
    {
230
        return $this->isReplaying;
231
    }
232
233
    public function replay(
234
        Collection $projectors,
235
        int $startingFromEventId = 0,
236
        callable $onEventReplayed = null
237
    ): void {
238
        $projectors = new EventHandlerCollection($projectors);
239
240
        $this->isReplaying = true;
241
242
        if ($startingFromEventId === 0) {
243
            $projectors->all()->each(function (Projector $projector) {
244
                if (method_exists($projector, 'resetState')) {
245
                    $projector->resetState();
246
                }
247
            });
248
        }
249
250
        event(new StartingEventReplay($projectors->all()));
251
252
        $projectors->call('onStartingEventReplay');
253
254
        app(StoredEventRepository::class)->retrieveAllStartingFrom($startingFromEventId)->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
255
            $this->applyStoredEventToProjectors(
256
                $storedEvent,
257
                $projectors->forEvent($storedEvent)
258
            );
259
260
            if ($onEventReplayed) {
261
                $onEventReplayed($storedEvent);
262
            }
263
        });
264
265
        $this->isReplaying = false;
266
267
        event(new FinishedEventReplay());
268
269
        $projectors->call('onFinishedEventReplay');
270
    }
271
}
272