Completed
Push — master ( c92ba1...102b41 )
by Freek
04:51
created

EventProjectionist::addReactor()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
1
<?php
2
3
namespace Spatie\EventProjector;
4
5
use Exception;
6
use Illuminate\Support\Collection;
7
use Spatie\EventProjector\Models\ProjectorStatus;
8
use Spatie\EventProjector\Models\StoredEvent;
9
use Spatie\EventProjector\Projectors\Projector;
10
use Spatie\EventProjector\EventHandlers\EventHandler;
11
use Spatie\EventProjector\Events\FinishedEventReplay;
12
use Spatie\EventProjector\Events\StartingEventReplay;
13
use Spatie\EventProjector\Exceptions\InvalidEventHandler;
14
use Spatie\EventProjector\Events\EventHandlerFailedHandlingEvent;
15
use Spatie\EventProjector\Events\ProjectorDidNotHandlePriorEvents;
16
17
class EventProjectionist
18
{
19
    /** @var \Illuminate\Support\Collection */
20
    protected $projectors;
21
22
    /** @var \Illuminate\Support\Collection */
23
    protected $reactors;
24
25
    /** @var bool */
26
    protected $isReplayingEvents = false;
27
28
    /** @var int */
29
    protected $config;
30
31
    public function __construct(array $config)
32
    {
33
        $this->projectors = collect();
34
35
        $this->reactors = collect();
36
37
        $this->config = $config;
0 ignored issues
show
Documentation Bug introduced by
It seems like $config of type array is incompatible with the declared type integer of property $config.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
38
    }
39
40
    public function isReplayingEvents(): bool
41
    {
42
        return $this->isReplayingEvents;
43
    }
44
45
    public function addProjector($projector): self
46
    {
47
        $this->guardAgainstInvalidEventHandler($projector);
48
49
        if ($this->alreadyAdded('projector', $projector)) {
50
            return $this;
51
        }
52
53
        $this->projectors->push($projector);
54
55
        return $this;
56
    }
57
58
    public function addProjectors(array $projectors): self
59
    {
60
        collect($projectors)->each(function ($projector) {
61
            $this->addProjector($projector);
62
        });
63
64
        return $this;
65
    }
66
67
    public function getProjectors(): Collection
68
    {
69
        return $this->projectors;
70
    }
71
72
    public function getProjector(string $name): ?Projector
73
    {
74
        return $this
75
            ->instantiate($this->projectors)
76
            ->first(function (Projector $projector) use ($name) {
77
                return $projector->getName() === $name;
78
            });
79
    }
80
81
    public function addReactor($reactor): self
82
    {
83
        $this->guardAgainstInvalidEventHandler($reactor);
84
85
        if ($this->alreadyAdded('reactor', $reactor)) {
86
            return $this;
87
        }
88
89
        $this->reactors->push($reactor);
90
91
        return $this;
92
    }
93
94
    public function addReactors(array $reactors): self
95
    {
96
        collect($reactors)->each(function ($reactor) {
97
            $this->addReactor($reactor);
98
        });
99
100
        return $this;
101
    }
102
103
    public function getReactors(): Collection
104
    {
105
        return $this->reactors;
106
    }
107
108
    public function storeEvent(ShouldBeStored $event)
109
    {
110
        $storedEvent = $this->config['stored_event_model']::createForEvent($event);
111
112
        $this->handleImmediately($storedEvent);
113
114
        dispatch(new HandleStoredEventJob($storedEvent))->onQueue($this->config['queue']);
115
    }
116
117
    public function handle(StoredEvent $storedEvent)
118
    {
119
        $this
120
            ->callEventHandlers($this->projectors, $storedEvent)
121
            ->callEventHandlers($this->reactors, $storedEvent);
122
    }
123
124
    public function handleImmediately(StoredEvent $storedEvent)
125
    {
126
        $projectors = $this->instantiate($this->projectors);
127
128
        $projectors = $projectors->filter->shouldBeCalledImmediately();
129
130
        $this->callEventHandlers($projectors, $storedEvent);
131
    }
132
133
    protected function callEventHandlers(Collection $eventHandlers, StoredEvent $storedEvent): self
134
    {
135
        $eventHandlers
136
            ->pipe(function (Collection $eventHandlers) {
137
                return $this->instantiate($eventHandlers);
138
            })
139
            ->filter(function (EventHandler $eventHandler) use ($storedEvent) {
140
                if (!$eventHandler instanceof Projector) {
141
                    return true;
142
                }
143
144
                return true;
145
            })
146
            ->filter(function (EventHandler $eventHandler) use ($storedEvent) {
147
                if (!$eventHandler instanceof Projector) {
148
                    return true;
149
                }
150
151
                if (!$method = $eventHandler->methodNameThatHandlesEvent($storedEvent->event)) {
152
                    return true;
153
                }
154
155
                if (!method_exists($eventHandler, $method)) {
156
                    throw InvalidEventHandler::eventHandlingMethodDoesNotExist($eventHandler, $storedEvent->event, $method);
0 ignored issues
show
Documentation introduced by
$eventHandler is of type object<Spatie\EventProje...r\Projectors\Projector>, but the function expects a object<Spatie\EventProjector\Exceptions\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
157
                }
158
159
                if (!$eventHandler->hasReceivedAllPriorEvents($storedEvent)) {
160
                    event(new ProjectorDidNotHandlePriorEvents($eventHandler, $storedEvent));
161
162
                    return false;
163
                }
164
165
                return true;
166
167
            })
168
            ->each(function (EventHandler $eventHandler) use ($storedEvent) {
169
                $eventWasHandledSuccessfully = $this->callEventHandler($eventHandler, $storedEvent);
170
171
                if (! $eventHandler instanceof Projector) {
172
                    return;
173
                }
174
175
                if (! $eventWasHandledSuccessfully) {
176
                    return;
177
                }
178
179
                if (! $eventHandler->handlesStreamOfStoredEvent($storedEvent)) {
180
                    return;
181
                }
182
183
                $eventHandler->rememberReceivedEvent($storedEvent);
184
            });
185
186
        return $this;
187
    }
188
189
    protected function callEventHandler(EventHandler $eventHandler, StoredEvent $storedEvent): bool
190
    {
191
        $event = $storedEvent->event;
192
193
        if (!$method = $eventHandler->methodNameThatHandlesEvent($event)) {
194
            return true;
195
        }
196
197
        try {
198
            app()->call([$eventHandler, $method], compact('event', 'storedEvent'));
199
        } catch (Exception $exception) {
200
            if (!$this->config['catch_exceptions']) {
201
                throw $exception;
202
            }
203
204
            $eventHandler->handleException($exception);
205
206
            event(new EventHandlerFailedHandlingEvent($eventHandler, $storedEvent, $exception));
207
208
            return false;
209
        }
210
211
        return true;
212
    }
213
214
    public function replayEvents(Collection $projectors, int $afterStoredEventId = 0, callable $onEventReplayed = null)
215
    {
216
        $this->isReplayingEvents = true;
217
218
        event(new StartingEventReplay($projectors));
219
220
        $projectors = $this->instantiate($projectors);
221
222
        $this->callMethod($projectors, 'onStartingEventReplay');
223
224
        StoredEvent::query()
225
            ->after($afterStoredEventId ?? 0)
226
            ->chunk($this->config['replay_chunk_size'], function (Collection $storedEvents) use ($projectors, $onEventReplayed) {
227
                $storedEvents->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
228
                    $this->callEventHandlers($projectors, $storedEvent);
229
230
                    if ($onEventReplayed) {
231
                        $onEventReplayed($storedEvent);
232
                    }
233
                });
234
            });
235
236
        $this->isReplayingEvents = false;
237
238
        event(new FinishedEventReplay());
239
240
        $this->callMethod($projectors, 'onFinishedEventReplay');
241
    }
242
243
    protected function guardAgainstInvalidEventHandler($eventHandler)
244
    {
245
        if (!is_string($eventHandler)) {
246
            return;
247
        }
248
249
        if (!class_exists($eventHandler)) {
250
            throw InvalidEventHandler::doesNotExist($eventHandler);
251
        }
252
    }
253
254
    protected function instantiate(Collection $eventHandlers)
255
    {
256
        return $eventHandlers->map(function ($eventHandler) {
257
            if (is_string($eventHandler)) {
258
                $eventHandler = app($eventHandler);
259
            }
260
261
            return $eventHandler;
262
        });
263
    }
264
265
    protected function callMethod(Collection $eventHandlers, string $method): self
266
    {
267
        $eventHandlers
268
            ->filter(function (EventHandler $eventHandler) use ($method) {
269
                return method_exists($eventHandler, $method);
270
            })
271
            ->each(function (EventHandler $eventHandler) use ($method) {
272
                return app()->call([$eventHandler, $method]);
273
            });
274
275
        return $this;
276
    }
277
278
    protected function alreadyAdded(string $type, $eventHandler)
279
    {
280
        $eventHandlerClassName = is_string($eventHandler)
281
            ? $eventHandler
282
            : get_class($eventHandler);
283
284
        $variableName = "{$type}s";
285
286
        $currentEventHandlers = $this->$variableName->toArray();
287
288
        if (in_array($eventHandlerClassName, $currentEventHandlers)) {
289
            return $this;
290
        }
291
    }
292
293
    protected function getClassNames(Collection $eventHandlers): array
294
    {
295
        return $eventHandlers
296
            ->map(function ($eventHandler) {
297
                if (is_string($eventHandler)) {
298
                    return $eventHandler;
299
                }
300
301
                return get_class($eventHandler);
302
            })
303
            ->toArray();
304
    }
305
}
306