Completed
Pull Request — master (#137)
by Sebastian
04:40
created

Projectionist::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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