Completed
Push — master ( 18d381...23e5bd )
by Freek
01:28
created

EventProjectionist::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
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
        $this->projectors->push($projector);
46
47
        return $this;
48
    }
49
50
    public function addProjectors(array $projectors): self
51
    {
52
        collect($projectors)->each(function ($projector) {
53
            $this->addProjector($projector);
54
        });
55
56
        return $this;
57
    }
58
59
    public function getProjectors(): Collection
60
    {
61
        return $this->projectors;
62
    }
63
64
    public function getProjector(string $name): ?Projector
65
    {
66
        return $this
67
            ->instantiate($this->projectors)
68
            ->first(function (Projector $projector) use ($name) {
69
                return $projector->getName() === $name;
70
            });
71
    }
72
73
    public function addReactor($reactor): self
74
    {
75
        $this->guardAgainstInvalidEventHandler($reactor);
76
77
        $this->reactors->push($reactor);
78
79
        return $this;
80
    }
81
82
    public function addReactors(array $reactors): self
83
    {
84
        collect($reactors)->each(function ($reactor) {
85
            $this->addReactor($reactor);
86
        });
87
88
        return $this;
89
    }
90
91
    public function handle(StoredEvent $storedEvent)
92
    {
93
        $this
94
            ->callEventHandlers($this->projectors, $storedEvent)
95
            ->callEventHandlers($this->reactors, $storedEvent);
96
    }
97
98
    protected function callEventHandlers(Collection $eventHandlers, StoredEvent $storedEvent): self
99
    {
100
        $eventHandlers
101
            ->pipe(function (Collection $eventHandler) {
102
                return $this->instantiate($eventHandler);
103
            })
104
            ->filter(function (object $eventHandler) use ($storedEvent) {
105
                if ($eventHandler instanceof Projector) {
106
                    if (!$eventHandler->hasReceivedAllPriorEvents($storedEvent)) {
107
                        event(new ProjectorDidNotHandlePriorEvents($eventHandler, $storedEvent));
108
109
                        return false;
110
                    }
111
                }
112
113
                return true;
114
            })
115
            ->each(function (object $eventHandler) use ($storedEvent) {
116
                $this->callEventHandler($eventHandler, $storedEvent);
117
118
                if ($eventHandler instanceof Projector) {
119
                    $eventHandler->rememberReceivedEvent($storedEvent);
120
                }
121
            });
122
123
        return $this;
124
    }
125
126
    protected function callEventHandler(object $eventHandler, StoredEvent $storedEvent)
127
    {
128
        if (!isset($eventHandler->handlesEvents)) {
129
            throw InvalidEventHandler::cannotHandleEvents($eventHandler);
130
        }
131
132
        $event = $storedEvent->event;
133
134
        if (!$method = $eventHandler->handlesEvents[get_class($event)] ?? false) {
135
            return;
136
        }
137
138
        if (!method_exists($eventHandler, $method)) {
139
            throw InvalidEventHandler::eventHandlingMethodDoesNotExist($eventHandler, $event, $method);
140
        }
141
142
        app()->call([$eventHandler, $method], compact('event', 'storedEvent'));
143
    }
144
145
    public function replayEvents(Collection $projectors, callable $onEventReplayed)
146
    {
147
        $this->isReplayingEvents = true;
148
149
        event(new StartingEventReplay());
150
151
        $projectors = $this
152
            ->instantiate($projectors)
153
            ->each->resetStatus();
154
155
        $this->callMethod($projectors, 'onStartingEventReplay');
156
157
        StoredEvent::chunk($this->replayChunkSize, function (Collection $storedEvents) use ($projectors, $onEventReplayed) {
158
            $storedEvents->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
159
                $this->callEventHandlers($projectors, $storedEvent);
160
161
                $onEventReplayed($storedEvent);
162
            });
163
        });
164
165
        $this->isReplayingEvents = false;
166
167
        event(new FinishedEventReplay());
168
169
        $this->callMethod($projectors, 'onFinishedEventReplay');
170
    }
171
172
    protected function guardAgainstInvalidEventHandler($eventHandler)
173
    {
174
        if (!is_string($eventHandler)) {
175
            return;
176
        }
177
178
        if (!class_exists($eventHandler)) {
179
            throw InvalidEventHandler::doesNotExist($eventHandler);
180
        }
181
    }
182
183
    protected function instantiate(Collection $eventHandlers)
184
    {
185
        return $eventHandlers->map(function ($eventHandler) {
186
            if (is_string($eventHandler)) {
187
                $eventHandler = app($eventHandler);
188
            }
189
190
            return $eventHandler;
191
        });
192
    }
193
194
    protected function callMethod(Collection $eventHandlers, string $method): self
195
    {
196
        $eventHandlers
197
            ->filter(function (object $eventHandler) use ($method) {
198
                return method_exists($eventHandler, $method);
199
            })
200
            ->each(function (object $eventHandler) use ($method) {
201
                return app()->call([$eventHandler, $method]);
202
            });
203
204
        return $this;
205
    }
206
}
207