Completed
Push — master ( e0b10e...a87d29 )
by Freek
11s
created

Projectionist::isProjecting()   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\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 $isProjecting = false;
30
31
    /** @var bool */
32
    protected $isReplaying = false;
33
34
    public function __construct(array $config = [])
35
    {
36
        $this->projectors = new EventHandlerCollection();
37
38
        $this->reactors = new EventHandlerCollection();
39
40
        $this->config = $config;
41
    }
42
43
    public function addProjector($projector): Projectionist
44
    {
45
        if (is_string($projector)) {
46
            $projector = app($projector);
47
        }
48
49
        if (! $projector instanceof Projector) {
50
            throw InvalidEventHandler::notAProjector($projector);
51
        }
52
53
        $this->projectors->add($projector);
54
55
        return $this;
56
    }
57
58
    public function addProjectors(array $projectors): Projectionist
59
    {
60
        foreach ($projectors as $projector) {
61
            $this->addProjector($projector);
62
        }
63
64
        return $this;
65
    }
66
67
    public function getProjectors(): Collection
68
    {
69
        return $this->projectors->all();
70
    }
71
72
    public function getProjector(string $name): ?Projector
73
    {
74
        return $this->projectors->all()->first(function (Projector $projector) use ($name) {
75
            return $projector->getName() === $name;
76
        });
77
    }
78
79
    public function addReactor($reactor): Projectionist
80
    {
81
        $this->reactors->add($reactor);
82
83
        return $this;
84
    }
85
86
    public function addReactors(array $reactors): Projectionist
87
    {
88
        foreach ($reactors as $reactor) {
89
            $this->addReactor($reactor);
90
        }
91
92
        return $this;
93
    }
94
95
    public function getReactors(): Collection
96
    {
97
        return $this->reactors->all();
98
    }
99
100
    public function storeEvent(ShouldBeStored $event)
101
    {
102
        $storedEvent = $this->getStoredEventClass()::createForEvent($event);
0 ignored issues
show
Bug introduced by
The method createForEvent cannot be called on $this->getStoredEventClass() (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
103
104
        $this->handleImmediately($storedEvent);
105
106
        if (method_exists($event, 'tags')) {
107
            $tags = $event->tags();
0 ignored issues
show
Bug introduced by
The method tags() does not seem to exist on object<Spatie\EventProjector\ShouldBeStored>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
108
        }
109
110
        dispatch(new HandleStoredEventJob($storedEvent, $tags ?? []))
111
            ->onQueue($this->config['queue']);
112
    }
113
114
    public function handle(StoredEvent $storedEvent)
115
    {
116
        $this->applyStoredEventToProjectors(
117
            $storedEvent,
118
            $this->projectors->forEvent($storedEvent)
119
        );
120
121
        $this->applyStoredEventToReactors(
122
            $storedEvent,
123
            $this->reactors->forEvent($storedEvent)
124
        );
125
    }
126
127
    public function handleImmediately(StoredEvent $storedEvent)
128
    {
129
        $projectors = $this->projectors
130
            ->forEvent($storedEvent)
131
            ->filter(function (Projector $projector) {
132
                return $projector->shouldBeCalledImmediately();
133
            });
134
135
        $this->applyStoredEventToProjectors($storedEvent, $projectors);
136
    }
137
138
    public function isProjecting(): bool
139
    {
140
        return $this->isProjecting;
141
    }
142
143
    protected function applyStoredEventToProjectors(StoredEvent $storedEvent, Collection $projectors)
144
    {
145
        $this->isProjecting = true;
146
147
        foreach ($projectors as $projector) {
148
            if ($projector->hasAlreadyReceivedEvent($storedEvent)) {
149
                continue;
150
            }
151
152
            if (! $projector->hasReceivedAllPriorEvents($storedEvent)) {
153
                event(new ProjectorDidNotHandlePriorEvents($projector, $storedEvent));
154
155
                $projector->markAsNotUpToDate($storedEvent);
156
157
                continue;
158
            }
159
160
            if (! $this->callEventHandler($projector, $storedEvent)) {
161
                continue;
162
            }
163
164
            $projector->rememberReceivedEvent($storedEvent);
165
        }
166
167
        $this->isProjecting = false;
168
    }
169
170
    protected function applyStoredEventToReactors(StoredEvent $storedEvent, Collection $reactors)
171
    {
172
        foreach ($reactors as $reactor) {
173
            $this->callEventHandler($reactor, $storedEvent);
174
        }
175
    }
176
177
    protected function callEventHandler(EventHandler $eventHandler, StoredEvent $storedEvent): bool
178
    {
179
        try {
180
            $eventHandler->handle($storedEvent);
181
        } catch (Exception $exception) {
182
            if (! $this->config['catch_exceptions']) {
183
                throw $exception;
184
            }
185
186
            $eventHandler->handleException($exception);
187
188
            event(new EventHandlerFailedHandlingEvent($eventHandler, $storedEvent, $exception));
189
190
            return false;
191
        }
192
193
        return true;
194
    }
195
196
    public function isReplaying(): bool
197
    {
198
        return $this->isReplaying;
199
    }
200
201
    public function replay(Collection $projectors, int $afterStoredEventId = 0, callable $onEventReplayed = null)
202
    {
203
        $this->isReplaying = true;
204
205
        $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...
206
207
        event(new StartingEventReplay($projectors->all()));
208
209
        $projectors->call('onStartingEventReplay');
210
211
        $this->getStoredEventClass()::query()
0 ignored issues
show
Bug introduced by
The method query cannot be called on $this->getStoredEventClass() (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
212
            ->after($afterStoredEventId ?? 0)
213
            ->chunk($this->config['replay_chunk_size'], function (Collection $storedEvents) use ($projectors, $onEventReplayed) {
214
                $storedEvents->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
215
                    $this->applyStoredEventToProjectors(
216
                        $storedEvent,
217
                        $projectors->forEvent($storedEvent)
218
                    );
219
220
                    if ($onEventReplayed) {
221
                        $onEventReplayed($storedEvent);
222
                    }
223
                });
224
            });
225
226
        $this->isReplaying = false;
227
228
        event(new FinishedEventReplay());
229
230
        $projectors->call('onFinishedEventReplay');
231
    }
232
233
    protected function getStoredEventClass(): string
234
    {
235
        return config('event-projector.stored_event_model');
236
    }
237
}
238