Completed
Pull Request — master (#148)
by Freek
04:33
created

Projectionist::addProjectors()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
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\Exceptions\InvalidEventHandler;
14
use Spatie\EventProjector\EventHandlers\EventHandlerCollection;
15
use Spatie\EventProjector\Events\EventHandlerFailedHandlingEvent;
16
use Spatie\EventProjector\Projectors\QueuedProjector;
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
        {
170
            $this->addEventHandler($eventHandler);
171
        }
172
    }
173
174
    public function handle(StoredEvent $storedEvent): void
175
    {
176
        $projectors = $this->projectors
177
            ->forEvent($storedEvent)
178
            ->reject(function (Projector $projector) {
179
                return $projector->shouldBeCalledImmediately();
180
            });
181
182
        $this->applyStoredEventToProjectors(
183
            $storedEvent,
184
            $projectors
185
        );
186
187
        $this->applyStoredEventToReactors(
188
            $storedEvent,
189
            $this->reactors->forEvent($storedEvent)
190
        );
191
    }
192
193
    public function handleWithSyncProjectors(StoredEvent $storedEvent): void
194
    {
195
        $projectors = $this->projectors
196
            ->forEvent($storedEvent)
197
            ->filter(function (Projector $projector) {
198
                return $projector->shouldBeCalledImmediately();
199
            });
200
201
        $this->applyStoredEventToProjectors($storedEvent, $projectors);
202
    }
203
204
    public function isProjecting(): bool
205
    {
206
        return $this->isProjecting;
207
    }
208
209
    private function applyStoredEventToProjectors(StoredEvent $storedEvent, Collection $projectors): void
210
    {
211
        $this->isProjecting = true;
212
213
        foreach ($projectors as $projector) {
214
            $this->callEventHandler($projector, $storedEvent);
215
        }
216
217
        $this->isProjecting = false;
218
    }
219
220
    private function applyStoredEventToReactors(StoredEvent $storedEvent, Collection $reactors): void
221
    {
222
        foreach ($reactors as $reactor) {
223
            $this->callEventHandler($reactor, $storedEvent);
224
        }
225
    }
226
227
    private function callEventHandler(EventHandler $eventHandler, StoredEvent $storedEvent): bool
228
    {
229
        try {
230
            $eventHandler->handle($storedEvent);
231
        } catch (Exception $exception) {
232
            if (! $this->catchExceptions) {
233
                throw $exception;
234
            }
235
236
            $eventHandler->handleException($exception);
237
238
            event(new EventHandlerFailedHandlingEvent($eventHandler, $storedEvent, $exception));
239
240
            return false;
241
        }
242
243
        return true;
244
    }
245
246
    public function isReplaying(): bool
247
    {
248
        return $this->isReplaying;
249
    }
250
251
    public function replay(
252
        Collection $projectors,
253
        int $startingFromEventId = 0,
254
        callable $onEventReplayed = null
255
    ): void {
256
        $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...
257
258
        $this->isReplaying = true;
259
260
        if ($startingFromEventId === 0) {
261
            $projectors->all()->each(function (Projector $projector) {
262
                if (method_exists($projector, 'resetState')) {
263
                    $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...
264
                }
265
            });
266
        }
267
268
        event(new StartingEventReplay($projectors->all()));
269
270
        $projectors->call('onStartingEventReplay');
271
272
        $this->storedEventClass::query()
0 ignored issues
show
Bug introduced by
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...
273
            ->startingFrom($startingFromEventId ?? 0)
274
            ->chunk($this->replayChunkSize, function (Collection $storedEvents) use ($projectors, $onEventReplayed) {
275
                $storedEvents->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
276
                    $this->applyStoredEventToProjectors(
277
                        $storedEvent,
278
                        $projectors->forEvent($storedEvent)
279
                    );
280
281
                    if ($onEventReplayed) {
282
                        $onEventReplayed($storedEvent);
283
                    }
284
                });
285
            });
286
287
        $this->isReplaying = false;
288
289
        event(new FinishedEventReplay());
290
291
        $projectors->call('onFinishedEventReplay');
292
    }
293
}
294