Completed
Push — master ( 676adf...d90d0b )
by Freek
01:27
created

EventProjectionist::replayEvents()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 14
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
12
class EventProjectionist
13
{
14
    /** @var \Illuminate\Support\Collection */
15
    public $projectors;
16
17
    /** @var \Illuminate\Support\Collection */
18
    public $reactors;
19
20
    /** @var bool */
21
    protected $isReplayingEvents = false;
22
23
    public function __construct()
24
    {
25
        $this->projectors = collect();
26
27
        $this->reactors = collect();
28
    }
29
30
    public function isReplayingEvents(): bool
31
    {
32
        return $this->isReplayingEvents;
33
    }
34
35
    public function addProjector($projector): self
36
    {
37
        $this->guardAgainstInvalidEventHandler($projector);
38
39
        $this->projectors->push($projector);
40
41
        return $this;
42
    }
43
44
    public function addProjectors(array $projectors): self
45
    {
46
        collect($projectors)->each(function ($projector) {
47
            $this->addProjector($projector);
48
        });
49
50
        return $this;
51
    }
52
53
    public function addReactor($reactor): self
54
    {
55
        $this->guardAgainstInvalidEventHandler($reactor);
56
57
        $this->reactors->push($reactor);
58
59
        return $this;
60
    }
61
62
    public function addReactors(array $reactors): self
63
    {
64
        collect($reactors)->each(function ($reactor) {
65
            $this->addReactor($reactor);
66
        });
67
68
        return $this;
69
    }
70
71
    public function callEventHandlers(Collection $eventHandlers, StoredEvent $storedEvent): self
72
    {
73
        $eventHandlers
74
            ->pipe(function (Collection $eventHandler) {
75
                return $this->instanciate($eventHandler);
76
            })
77
            ->filter(function (object $eventHandler) use ($storedEvent) {
78
                if ($eventHandler instanceof Projector) {
79
                    return $eventHandler->hasReceivedAllPriorEvents($storedEvent);
80
                }
81
82
                return true;
83
            })
84
            ->each(function (object $eventHandler) use ($storedEvent) {
85
                $this->callEventHandler($eventHandler, $storedEvent);
86
87
                if ($eventHandler instanceof Projector) {
88
                    $eventHandler->rememberReceivedEvent($storedEvent);
89
                }
90
            });
91
92
        return $this;
93
    }
94
95
    protected function callEventHandler(object $eventHandler, StoredEvent $storedEvent)
96
    {
97
        if (!isset($eventHandler->handlesEvents)) {
98
            throw InvalidEventHandler::cannotHandleEvents($eventHandler);
99
        }
100
101
        $event = $storedEvent->event;
102
103
        if (!$method = $eventHandler->handlesEvents[get_class($event)] ?? false) {
104
            return;
105
        }
106
107
        if (!method_exists($eventHandler, $method)) {
108
            throw InvalidEventHandler::eventHandlingMethodDoesNotExist($eventHandler, $event, $method);
109
        }
110
111
        app()->call([$eventHandler, $method], compact('event'));
112
    }
113
114
    public function replayEvents(Collection $projectors, callable $onEventReplayed)
115
    {
116
        $this->isReplayingEvents = true;
117
118
        event(new StartingEventReplay());
119
120
        $projectors = $this
121
            ->instanciate($projectors)
122
            ->each->resetStatus();
123
124
        $this->callMethod($projectors, 'onStartingEventReplay');
125
126
        StoredEvent::chunk(1000, function (Collection $storedEvents) use ($projectors, $onEventReplayed) {
127
            $storedEvents->each(function (StoredEvent $storedEvent) use ($projectors, $onEventReplayed) {
128
                $this->callEventHandlers($projectors, $storedEvent);
129
130
                $onEventReplayed($storedEvent);
131
            });
132
        });
133
134
        $this->isReplayingEvents = false;
135
136
        event(new FinishedEventReplay());
137
138
        $this->callMethod($projectors, 'onFinishedEventReplay');
139
    }
140
141
    protected function guardAgainstInvalidEventHandler($projector)
142
    {
143
        if (!is_string($projector)) {
144
            return;
145
        }
146
147
        if (!class_exists($projector)) {
148
            throw InvalidEventHandler::doesNotExist($projector);
149
        }
150
    }
151
152
    protected function instanciate(Collection $eventHandlers)
153
    {
154
        return $eventHandlers->map(function ($eventHandler) {
155
            if (is_string($eventHandler)) {
156
                $eventHandler = app($eventHandler);
157
            }
158
159
            return $eventHandler;
160
        });
161
    }
162
163
    protected function callMethod(Collection $eventHandlers, string $method): self
164
    {
165
        $eventHandlers
166
            ->filter(function (object $eventHandler) use ($method) {
167
                return method_exists($eventHandler, $method);
168
            })
169
            ->each(function (object $eventHandler) use ($method) {
170
                return app()->call([$eventHandler, $method]);
171
            });
172
173
        return $this;
174
    }
175
}
176