Completed
Push — master ( 47f9d7...a7d6eb )
by Freek
01:50 queued 10s
created

src/Projectionist.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

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...
263
                }
264
            });
265
        }
266
267
        event(new StartingEventReplay($projectors->all()));
268
269
        $projectors->call('onStartingEventReplay');
270
271
        $this->storedEventClass::query()
0 ignored issues
show
The method query cannot be called on $this->storedEventClass (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...
272
            ->startingFrom($startingFromEventId ?? 0)
273
            ->chunk($this->replayChunkSize, function (Collection $storedEvents) use ($projectors, $onEventReplayed) {
274
                $storedEvents->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
275
                    $this->applyStoredEventToProjectors(
276
                        $storedEvent,
277
                        $projectors->forEvent($storedEvent)
278
                    );
279
280
                    if ($onEventReplayed) {
281
                        $onEventReplayed($storedEvent);
282
                    }
283
                });
284
            });
285
286
        $this->isReplaying = false;
287
288
        event(new FinishedEventReplay());
289
290
        $projectors->call('onFinishedEventReplay');
291
    }
292
}
293