Completed
Push — master ( 12d4c0...f318b1 )
by Sebastian
05:50
created

Projectionist::getProjectors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Spatie\EventProjector;
4
5
use Exception;
6
use Illuminate\Support\Collection;
7
use Spatie\EventProjector\Models\StoredEvent;
8
use Spatie\EventProjector\Projectors\Projector;
9
use Spatie\EventProjector\EventHandlers\EventHandler;
10
use Spatie\EventProjector\Events\FinishedEventReplay;
11
use Spatie\EventProjector\Events\StartingEventReplay;
12
use Spatie\EventProjector\Exceptions\InvalidEventHandler;
13
use Spatie\EventProjector\Events\EventHandlerFailedHandlingEvent;
14
use Spatie\EventProjector\Events\ProjectorDidNotHandlePriorEvents;
15
16
class Projectionist
17
{
18
    /** @var \Illuminate\Support\Collection */
19
    protected $projectors;
20
21
    /** @var \Illuminate\Support\Collection */
22
    protected $reactors;
23
24
    /** @var bool */
25
    protected $isReplayingEvents = false;
26
27
    /** @var int */
28
    protected $config;
29
30
    public function __construct(array $config)
31
    {
32
        $this->projectors = collect();
33
34
        $this->reactors = collect();
35
36
        $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...
37
    }
38
39
    public function isReplayingEvents(): bool
40
    {
41
        return $this->isReplayingEvents;
42
    }
43
44
    public function addProjector($projector): Projectionist
45
    {
46
        $this->guardAgainstInvalidEventHandler($projector);
47
48
        if ($this->alreadyAdded('projector', $projector)) {
49
            return $this;
50
        }
51
52
        $this->projectors->push($projector);
53
54
        return $this;
55
    }
56
57
    public function addProjectors(array $projectors): Projectionist
58
    {
59
        collect($projectors)->each(function ($projector) {
60
            $this->addProjector($projector);
61
        });
62
63
        return $this;
64
    }
65
66
    public function getProjectors(): Collection
67
    {
68
        return $this->instantiate($this->projectors);
69
    }
70
71
    public function getProjector(string $name): ?Projector
72
    {
73
        return $this
74
            ->instantiate($this->projectors)
75
            ->first(function (Projector $projector) use ($name) {
76
                return $projector->getName() === $name;
77
            });
78
    }
79
80
    public function addReactor($reactor): Projectionist
81
    {
82
        $this->guardAgainstInvalidEventHandler($reactor);
83
84
        if ($this->alreadyAdded('reactor', $reactor)) {
85
            return $this;
86
        }
87
88
        $this->reactors->push($reactor);
89
90
        return $this;
91
    }
92
93
    public function addReactors(array $reactors): Projectionist
94
    {
95
        collect($reactors)->each(function ($reactor) {
96
            $this->addReactor($reactor);
97
        });
98
99
        return $this;
100
    }
101
102
    public function getReactors(): Collection
103
    {
104
        return $this->reactors;
105
    }
106
107
    public function storeEvent(ShouldBeStored $event)
108
    {
109
        $storedEvent = $this->config['stored_event_model']::createForEvent($event);
110
111
        $this->handleImmediately($storedEvent);
112
113
        dispatch(new HandleStoredEventJob($storedEvent))->onQueue($this->config['queue']);
114
    }
115
116
    public function handle(StoredEvent $storedEvent)
117
    {
118
        $this
119
            ->callEventHandlers($this->projectors, $storedEvent)
120
            ->callEventHandlers($this->reactors, $storedEvent);
121
    }
122
123
    public function handleImmediately(StoredEvent $storedEvent)
124
    {
125
        $projectors = $this->instantiate($this->projectors);
126
127
        $projectors = $projectors->filter->shouldBeCalledImmediately();
128
129
        $this->callEventHandlers($projectors, $storedEvent);
130
    }
131
132
    protected function callEventHandlers(Collection $eventHandlers, StoredEvent $storedEvent): Projectionist
133
    {
134
        $eventHandlers
135
            ->pipe(function (Collection $eventHandlers) {
136
                return $this->instantiate($eventHandlers);
137
            })
138
            ->filter(function (EventHandler $eventHandler) use ($storedEvent) {
139
                if (! $method = $eventHandler->methodNameThatHandlesEvent($storedEvent->event)) {
140
                    return false;
141
                }
142
143
                if (! method_exists($eventHandler, $method)) {
144
                    throw InvalidEventHandler::eventHandlingMethodDoesNotExist($eventHandler, $storedEvent->event, $method);
0 ignored issues
show
Documentation introduced by
$eventHandler is of type object<Spatie\EventProje...tHandlers\EventHandler>, 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...
145
                }
146
147
                return true;
148
            })
149
            ->filter(function (EventHandler $eventHandler) use ($storedEvent) {
150
                if (! $eventHandler instanceof Projector) {
151
                    return true;
152
                }
153
154
                if ($eventHandler->hasAlreadyReceivedEvent($storedEvent)) {
155
                    return false;
156
                }
157
158
                if (! $eventHandler->hasReceivedAllPriorEvents($storedEvent)) {
159
                    event(new ProjectorDidNotHandlePriorEvents($eventHandler, $storedEvent));
160
161
                    $eventHandler->markAsNotUpToDate($storedEvent);
162
163
                    return false;
164
                }
165
166
                return true;
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
                $eventHandler->rememberReceivedEvent($storedEvent);
180
            });
181
182
        return $this;
183
    }
184
185
    protected function callEventHandler(EventHandler $eventHandler, StoredEvent $storedEvent): bool
186
    {
187
        $event = $storedEvent->event;
188
189
        $method = $eventHandler->methodNameThatHandlesEvent($event);
190
191
        try {
192
            app()->call([$eventHandler, $method], compact('event', 'storedEvent'));
193
        } catch (Exception $exception) {
194
            if (! $this->config['catch_exceptions']) {
195
                throw $exception;
196
            }
197
198
            $eventHandler->handleException($exception);
199
200
            event(new EventHandlerFailedHandlingEvent($eventHandler, $storedEvent, $exception));
201
202
            return false;
203
        }
204
205
        return true;
206
    }
207
208
    public function replayEvents(Collection $projectors, int $afterStoredEventId = 0, callable $onEventReplayed = null)
209
    {
210
        $this->isReplayingEvents = true;
211
212
        event(new StartingEventReplay($projectors));
213
214
        $projectors = $this->instantiate($projectors);
215
216
        $this->callMethod($projectors, 'onStartingEventReplay');
217
218
        StoredEvent::query()
219
            ->after($afterStoredEventId ?? 0)
220
            ->chunk($this->config['replay_chunk_size'], function (Collection $storedEvents) use ($projectors, $onEventReplayed) {
221
                $storedEvents->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
222
                    $this->callEventHandlers($projectors, $storedEvent);
223
224
                    if ($onEventReplayed) {
225
                        $onEventReplayed($storedEvent);
226
                    }
227
                });
228
            });
229
230
        $this->isReplayingEvents = false;
231
232
        event(new FinishedEventReplay());
233
234
        $this->callMethod($projectors, 'onFinishedEventReplay');
235
    }
236
237
    protected function guardAgainstInvalidEventHandler($eventHandler)
238
    {
239
        if (! is_string($eventHandler)) {
240
            return;
241
        }
242
243
        if (! class_exists($eventHandler)) {
244
            throw InvalidEventHandler::doesNotExist($eventHandler);
245
        }
246
    }
247
248
    protected function instantiate(Collection $eventHandlers)
249
    {
250
        return $eventHandlers->map(function ($eventHandler) {
251
            if (is_string($eventHandler)) {
252
                $eventHandler = app($eventHandler);
253
            }
254
255
            return $eventHandler;
256
        });
257
    }
258
259
    protected function callMethod(Collection $eventHandlers, string $method): Projectionist
260
    {
261
        $eventHandlers
262
            ->filter(function (EventHandler $eventHandler) use ($method) {
263
                return method_exists($eventHandler, $method);
264
            })
265
            ->each(function (EventHandler $eventHandler) use ($method) {
266
                return app()->call([$eventHandler, $method]);
267
            });
268
269
        return $this;
270
    }
271
272
    protected function alreadyAdded(string $type, $eventHandler)
273
    {
274
        $eventHandlerClassName = is_string($eventHandler)
275
            ? $eventHandler
276
            : get_class($eventHandler);
277
278
        $variableName = "{$type}s";
279
280
        $currentEventHandlers = $this->$variableName->toArray();
281
282
        if (in_array($eventHandlerClassName, $currentEventHandlers)) {
283
            return $this;
284
        }
285
    }
286
287
    protected function getClassNames(Collection $eventHandlers): array
288
    {
289
        return $eventHandlers
290
            ->map(function ($eventHandler) {
291
                if (is_string($eventHandler)) {
292
                    return $eventHandler;
293
                }
294
295
                return get_class($eventHandler);
296
            })
297
            ->toArray();
298
    }
299
}
300