Projectionist   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 267
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 9
dl 0
loc 267
rs 9.28
c 0
b 0
f 0

20 Methods

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