Completed
Push — master ( c9545d...9b8f2e )
by Freek
01:31
created

EventProjectionist::callMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 7
nc 1
nop 2
1
<?php
2
3
namespace Spatie\EventProjector;
4
5
use Illuminate\Support\Collection;
6
use Spatie\EventProjector\Models\StoredEvent;
7
use Spatie\EventProjector\Projectors\Projector;
8
use Spatie\EventProjector\Events\FinishedEventReplay;
9
use Spatie\EventProjector\Events\StartingEventReplay;
10
use Spatie\EventProjector\Exceptions\InvalidEventHandler;
11
use Spatie\EventProjector\Events\ProjectorDidNotHandlePriorEvents;
12
13
class EventProjectionist
14
{
15
    /** @var \Illuminate\Support\Collection */
16
    protected $projectors;
17
18
    /** @var \Illuminate\Support\Collection */
19
    protected $reactors;
20
21
    /** @var bool */
22
    protected $isReplayingEvents = false;
23
24
    /** @var int */
25
    protected $replayChunkSize;
26
27
    public function __construct(array $config)
28
    {
29
        $this->projectors = collect();
30
31
        $this->reactors = collect();
32
33
        $this->replayChunkSize = $config['replay_chunk_size'] ?? 1000;
34
    }
35
36
    public function isReplayingEvents(): bool
37
    {
38
        return $this->isReplayingEvents;
39
    }
40
41
    public function addProjector($projector): self
42
    {
43
        $this->guardAgainstInvalidEventHandler($projector);
44
45
        if ($this->alreadyAdded('projector', $projector)) {
46
            return $this;
47
        }
48
49
        $this->projectors->push($projector);
50
51
        return $this;
52
    }
53
54
    public function addProjectors(array $projectors): self
55
    {
56
        collect($projectors)->each(function ($projector) {
57
            $this->addProjector($projector);
58
        });
59
60
        return $this;
61
    }
62
63
    public function getProjectors(): Collection
64
    {
65
        return $this->projectors;
66
    }
67
68
    public function getProjector(string $name): ?Projector
69
    {
70
        return $this
71
            ->instantiate($this->projectors)
72
            ->first(function (Projector $projector) use ($name) {
73
                return $projector->getName() === $name;
74
            });
75
    }
76
77
    public function addReactor($reactor): self
78
    {
79
        $this->guardAgainstInvalidEventHandler($reactor);
80
81
        if ($this->alreadyAdded('reactor', $reactor)) {
82
            return $this;
83
        }
84
85
        $this->reactors->push($reactor);
86
87
        return $this;
88
    }
89
90
    public function addReactors(array $reactors): self
91
    {
92
        collect($reactors)->each(function ($reactor) {
93
            $this->addReactor($reactor);
94
        });
95
96
        return $this;
97
    }
98
99
    public function getReactors(): Collection
100
    {
101
        return $this->reactors;
102
    }
103
104
    public function handle(StoredEvent $storedEvent)
105
    {
106
        $this
107
            ->callEventHandlers($this->projectors, $storedEvent)
108
            ->callEventHandlers($this->reactors, $storedEvent);
109
    }
110
111
    protected function callEventHandlers(Collection $eventHandlers, StoredEvent $storedEvent): self
112
    {
113
        $eventHandlers
114
            ->pipe(function (Collection $eventHandler) {
115
                return $this->instantiate($eventHandler);
116
            })
117
            ->filter(function (object $eventHandler) use ($storedEvent) {
118
                if ($eventHandler instanceof Projector) {
119
                    if (!$eventHandler->hasReceivedAllPriorEvents($storedEvent)) {
120
                        event(new ProjectorDidNotHandlePriorEvents($eventHandler, $storedEvent));
121
122
                        return false;
123
                    }
124
                }
125
126
                return true;
127
            })
128
            ->each(function (object $eventHandler) use ($storedEvent) {
129
                $this->callEventHandler($eventHandler, $storedEvent);
130
131
                if ($eventHandler instanceof Projector) {
132
                    $eventHandler->rememberReceivedEvent($storedEvent);
133
                }
134
            });
135
136
        return $this;
137
    }
138
139
    protected function callEventHandler(object $eventHandler, StoredEvent $storedEvent)
140
    {
141
        if (!isset($eventHandler->handlesEvents)) {
142
            throw InvalidEventHandler::cannotHandleEvents($eventHandler);
143
        }
144
145
        $event = $storedEvent->event;
146
147
        if (!$method = $eventHandler->handlesEvents[get_class($event)] ?? false) {
148
            return;
149
        }
150
151
        if (!method_exists($eventHandler, $method)) {
152
            throw InvalidEventHandler::eventHandlingMethodDoesNotExist($eventHandler, $event, $method);
153
        }
154
155
        app()->call([$eventHandler, $method], compact('event', 'storedEvent'));
156
    }
157
158
    public function replayEvents(Collection $projectors, callable $onEventReplayed)
159
    {
160
        $this->isReplayingEvents = true;
161
162
        event(new StartingEventReplay());
163
164
        $projectors = $this
165
            ->instantiate($projectors)
166
            ->each->resetStatus();
167
168
        $this->callMethod($projectors, 'onStartingEventReplay');
169
170
        StoredEvent::chunk($this->replayChunkSize, function (Collection $storedEvents) use ($projectors, $onEventReplayed) {
171
            $storedEvents->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
172
                $this->callEventHandlers($projectors, $storedEvent);
173
174
                $onEventReplayed($storedEvent);
175
            });
176
        });
177
178
        $this->isReplayingEvents = false;
179
180
        event(new FinishedEventReplay());
181
182
        $this->callMethod($projectors, 'onFinishedEventReplay');
183
    }
184
185
    protected function guardAgainstInvalidEventHandler($eventHandler)
186
    {
187
        if (!is_string($eventHandler)) {
188
            return;
189
        }
190
191
        if (!class_exists($eventHandler)) {
192
            throw InvalidEventHandler::doesNotExist($eventHandler);
193
        }
194
    }
195
196
    protected function instantiate(Collection $eventHandlers)
197
    {
198
        return $eventHandlers->map(function ($eventHandler) {
199
            if (is_string($eventHandler)) {
200
                $eventHandler = app($eventHandler);
201
            }
202
203
            return $eventHandler;
204
        });
205
    }
206
207
    protected function callMethod(Collection $eventHandlers, string $method): self
208
    {
209
        $eventHandlers
210
            ->filter(function (object $eventHandler) use ($method) {
211
                return method_exists($eventHandler, $method);
212
            })
213
            ->each(function (object $eventHandler) use ($method) {
214
                return app()->call([$eventHandler, $method]);
215
            });
216
217
        return $this;
218
    }
219
220
221
222
    protected function alreadyAdded(string $type, $eventHandler)
223
    {
224
        $eventHandlerClassName = is_string($eventHandler)
225
            ? $eventHandler
226
            : get_class($eventHandler);
227
228
        $variableName = "{$type}s";
229
230
        $currentEventHandlers = $this->$variableName->toArray();
231
232
        if (in_array($eventHandlerClassName, $currentEventHandlers)) {
233
            return $this;
234
        }
235
    }
236
237
    protected function getClassNames(Collection $eventHandlers): array
238
    {
239
        return $eventHandlers
240
            ->map(function ($eventHandler) {
241
                if (is_string($eventHandler)) {
242
                    return $eventHandler;
243
                }
244
245
                return get_class($eventHandler);
246
            })
247
            ->toArray();
248
    }
249
}
250