Completed
Push — master ( bbac6a...7e9533 )
by Freek
07:33 queued 04:38
created

Projectionist::replay()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 32
rs 9.408
c 0
b 0
f 0
cc 2
nc 1
nop 3
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\EventHandlers\EventHandlerCollection;
14
use Spatie\EventProjector\Events\EventHandlerFailedHandlingEvent;
15
use Spatie\EventProjector\Events\ProjectorDidNotHandlePriorEvents;
16
17
class Projectionist
18
{
19
    /** @var array */
20
    protected $config;
21
22
    /** @var \Spatie\EventProjector\EventHandlers\EventHandlerCollection */
23
    protected $projectors;
24
25
    /** @var \Spatie\EventProjector\EventHandlers\EventHandlerCollection */
26
    protected $reactors;
27
28
    /** @var bool */
29
    protected $isReplaying = false;
30
31
    public function __construct(array $config)
32
    {
33
        $this->projectors = new EventHandlerCollection();
34
35
        $this->reactors = new EventHandlerCollection();
36
37
        $this->config = $config;
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 addProjectors(array $projectors): Projectionist
56
    {
57
        foreach ($projectors as $projector) {
58
            $this->addProjector($projector);
59
        }
60
61
        return $this;
62
    }
63
64
    public function getProjectors(): Collection
65
    {
66
        return $this->projectors->all();
67
    }
68
69
    public function getProjector(string $name): ?Projector
70
    {
71
        return $this->projectors->all()->first(function (Projector $projector) use ($name) {
72
            return $projector->getName() === $name;
73
        });
74
    }
75
76
    public function addReactor($reactor): Projectionist
77
    {
78
        $this->reactors->add($reactor);
79
80
        return $this;
81
    }
82
83
    public function addReactors(array $reactors): Projectionist
84
    {
85
        foreach ($reactors as $reactor) {
86
            $this->addReactor($reactor);
87
        }
88
89
        return $this;
90
    }
91
92
    public function getReactors(): Collection
93
    {
94
        return $this->reactors->all();
95
    }
96
97
    public function storeEvent(ShouldBeStored $event)
98
    {
99
        $storedEvent = $this->config['stored_event_model']::createForEvent($event);
100
101
        $this->handleImmediately($storedEvent);
102
103
        dispatch(new HandleStoredEventJob($storedEvent))
104
            ->onQueue($this->config['queue']);
105
    }
106
107
    public function handle(StoredEvent $storedEvent)
108
    {
109
        $this->applyStoredEventToProjectors(
110
            $storedEvent,
111
            $this->projectors->forEvent($storedEvent)
112
        );
113
114
        $this->applyStoredEventToReactors(
115
            $storedEvent,
116
            $this->reactors->forEvent($storedEvent)
117
        );
118
    }
119
120
    public function handleImmediately(StoredEvent $storedEvent)
121
    {
122
        $projectors = $this->projectors
123
            ->forEvent($storedEvent)
124
            ->filter(function (Projector $projector) {
125
                return $projector->shouldBeCalledImmediately();
126
            });
127
128
        $this->applyStoredEventToProjectors($storedEvent, $projectors);
129
    }
130
131
    protected function applyStoredEventToProjectors(StoredEvent $storedEvent, Collection $projectors)
132
    {
133
        foreach ($projectors as $projector) {
134
            if ($projector->hasAlreadyReceivedEvent($storedEvent)) {
135
                continue;
136
            }
137
138
            if (! $projector->hasReceivedAllPriorEvents($storedEvent)) {
139
                event(new ProjectorDidNotHandlePriorEvents($projector, $storedEvent));
140
141
                $projector->markAsNotUpToDate($storedEvent);
142
143
                continue;
144
            }
145
146
            if (! $this->callEventHandler($projector, $storedEvent)) {
147
                continue;
148
            }
149
150
            $projector->rememberReceivedEvent($storedEvent);
151
        }
152
    }
153
154
    protected function applyStoredEventToReactors(StoredEvent $storedEvent, Collection $reactors)
155
    {
156
        foreach ($reactors as $reactor) {
157
            $this->callEventHandler($reactor, $storedEvent);
158
        }
159
    }
160
161
    protected function callEventHandler(EventHandler $eventHandler, StoredEvent $storedEvent): bool
162
    {
163
        try {
164
            $eventHandler->handle($storedEvent);
165
        } catch (Exception $exception) {
166
            if (! $this->config['catch_exceptions']) {
167
                throw $exception;
168
            }
169
170
            $eventHandler->handleException($exception);
171
172
            event(new EventHandlerFailedHandlingEvent($eventHandler, $storedEvent, $exception));
173
174
            return false;
175
        }
176
177
        return true;
178
    }
179
180
    public function isReplaying(): bool
181
    {
182
        return $this->isReplaying;
183
    }
184
185
    public function replay(Collection $projectors, int $afterStoredEventId = 0, callable $onEventReplayed = null)
186
    {
187
188
        $this->isReplaying = true;
189
190
        $projectors = new EventHandlerCollection($projectors);
0 ignored issues
show
Documentation introduced by
$projectors is of type object<Illuminate\Support\Collection>, but the function expects a array.

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...
191
192
        event(new StartingEventReplay($projectors->all()));
193
194
        $projectors->call('onStartingEventReplay');
195
196
        StoredEvent::query()
197
            ->after($afterStoredEventId ?? 0)
198
            ->chunk($this->config['replay_chunk_size'], function (Collection $storedEvents) use ($projectors, $onEventReplayed) {
199
                $storedEvents->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
200
                    $this->applyStoredEventToProjectors(
201
                        $storedEvent,
202
                        $projectors->forEvent($storedEvent)
203
                    );
204
205
                    if ($onEventReplayed) {
206
                        $onEventReplayed($storedEvent);
207
                    }
208
                });
209
            });
210
211
        $this->isReplaying = false;
212
213
        event(new FinishedEventReplay());
214
215
        $projectors->call('onFinishedEventReplay');
216
    }
217
}
218