Completed
Push — master ( fa1726...5762e2 )
by Freek
03:49 queued 01:51
created

ProjectsEvents   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 5

Importance

Changes 0
Metric Value
wmc 31
lcom 3
cbo 5
dl 0
loc 207
rs 9.92
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 8 2
B rememberReceivedEvent() 0 46 6
A hasAlreadyReceivedEvent() 0 15 3
B hasReceivedAllPriorEvents() 0 47 5
A hasReceivedAllEvents() 0 4 1
A markAsNotUpToDate() 0 8 2
A getLastProcessedEventId() 0 4 1
A lastEventProcessedAt() 0 4 1
A reset() 0 10 2
A shouldBeCalledImmediately() 0 4 1
A getEventStreams() 0 17 3
A getEventStreamFullNames() 0 14 2
A getStatus() 0 4 1
A getAllStatuses() 0 4 1
1
<?php
2
3
namespace Spatie\EventProjector\Projectors;
4
5
use Carbon\Carbon;
6
use Illuminate\Support\Collection;
7
use Spatie\EventProjector\Models\StoredEvent;
8
use Spatie\EventProjector\Models\ProjectorStatus;
9
use Spatie\EventProjector\EventHandlers\HandlesEvents;
10
use Spatie\EventProjector\Exceptions\CouldNotResetProjector;
11
12
trait ProjectsEvents
13
{
14
    use HandlesEvents;
15
16
    public function getName(): string
17
    {
18
        if (isset($this->name)) {
19
            return $this->name;
0 ignored issues
show
Bug introduced by
The property name does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
20
        }
21
22
        return get_class($this);
23
    }
24
25
    public function rememberReceivedEvent(StoredEvent $storedEvent)
26
    {
27
        foreach ($this->getEventStreamFullNames($storedEvent) as $streamName) {
28
            $status = $this->getStatus($streamName);
29
30
            $status->rememberLastProcessedEvent($storedEvent, $this);
31
32
            $streams = $this->getEventStreams($storedEvent);
33
34
            if ($streams->isEmpty()) {
35
                $lastStoredEvent = StoredEvent::query()
36
                    ->whereIn('event_class', $this->handlesEventClassNames())
37
                    ->orderBy('id', 'desc')
38
                    ->first();
39
40
                $lastStoredEventId = (int) optional($lastStoredEvent)->id ?? 0;
41
42
                $status = $this->getStatus();
43
44
                $status->last_processed_event_id === $lastStoredEventId
45
                    ? $status->markAsReceivedAllEvents()
46
                    : $status->markasAsNotReceivedAllEvents();
47
48
                return;
49
            }
50
51
            foreach ($streams as $streamName => $streamValue) {
52
                $streamFullName = "{$streamName}-{$streamValue}";
53
                $whereJsonClause = str_replace('.', '->', $streamName);
54
55
                $lastStoredEvent = StoredEvent::query()
56
                    ->whereIn('event_class', $this->handlesEventClassNames())
57
                    ->where("event_properties->{$whereJsonClause}", $streamValue)
58
                    ->orderBy('id', 'desc')
59
                    ->first();
60
61
                $lastStoredEventId = (int)optional($lastStoredEvent)->id ?? 0;
62
63
                $status = $this->getStatus($streamFullName);
64
65
                $status->last_processed_event_id === $lastStoredEventId
66
                    ? $status->markAsReceivedAllEvents()
67
                    : $status->markasAsNotReceivedAllEvents();
68
            }
69
        }
70
    }
71
72
    public function hasAlreadyReceivedEvent(StoredEvent $storedEvent): bool
73
    {
74
        foreach($this->getEventStreamFullNames($storedEvent) as $streamFullName)
75
        {
76
            $status = $this->getStatus($streamFullName);
77
78
            $lastProcessedEventId = (int) optional($status)->last_processed_event_id ?? 0;
79
80
            if ($storedEvent->id <= $lastProcessedEventId) {
81
                return true;
82
            }
83
        }
84
85
        return false;
86
    }
87
88
    public function hasReceivedAllPriorEvents(StoredEvent $storedEvent): bool
89
    {
90
        $streams = $this->getEventStreams($storedEvent);
91
92
        if ($streams->isEmpty()) {
93
            $lastStoredEvent = StoredEvent::query()
94
                ->whereIn('event_class', $this->handlesEventClassNames())
95
                ->where('id', '<', $storedEvent->id)
96
                ->orderBy('id', 'desc')
97
                ->first();
98
99
            $lastStoredEventId = (int) optional($lastStoredEvent)->id ?? 0;
100
101
            $status = $this->getStatus();
102
103
            $lastProcessedEventId = (int) $status->last_processed_event_id ?? 0;
104
105
            if ($lastStoredEventId !== $lastProcessedEventId) {
106
                return false;
107
108
            }
109
            return true;
110
        }
111
112
        foreach ($streams as $streamName => $streamValue) {
113
            $streamFullName = "{$streamName}-{$streamValue}";
114
            $whereJsonClause = str_replace('.', '->', $streamName);
115
116
            $lastStoredEvent = StoredEvent::query()
117
                ->whereIn('event_class', $this->handlesEventClassNames())
118
                ->where('id', '<', $storedEvent->id)
119
                ->where("event_properties->{$whereJsonClause}", $streamValue)
120
                ->orderBy('id', 'desc')
121
                ->first();
122
123
            $lastStoredEventId = (int) optional($lastStoredEvent)->id ?? 0;
124
125
            $status = $this->getStatus($streamFullName);
126
            $lastProcessedEventId = (int) $status->last_processed_event_id ?? 0;
127
128
            if ($lastStoredEventId !== $lastProcessedEventId) {
129
                return false;
130
            }
131
        }
132
133
        return true;
134
    }
135
136
    public function hasReceivedAllEvents(): bool
137
    {
138
        return ProjectorStatus::hasReceivedAllEvents($this);
139
    }
140
141
    public function markAsNotUpToDate(StoredEvent $storedEvent)
142
    {
143
        foreach($this->getEventStreamFullNames($storedEvent) as $streamName) {
144
            $status = $this->getStatus($streamName);
145
146
            $status->markasAsNotReceivedAllEvents();
147
        }
148
    }
149
150
    public function getLastProcessedEventId(): int
151
    {
152
        return $this->getStatus()->last_processed_event_id ?? 0;
153
    }
154
155
    public function lastEventProcessedAt(): Carbon
156
    {
157
        return $this->getStatus()->updated_at;
158
    }
159
160
    public function reset()
161
    {
162
        if (! method_exists($this, 'resetState')) {
163
            throw CouldNotResetProjector::doesNotHaveResetStateMethod($this);
164
        }
165
166
        $this->resetState();
0 ignored issues
show
Bug introduced by
It seems like resetState() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
167
168
        $this->getAllStatuses()->each->delete();
169
    }
170
171
    public function shouldBeCalledImmediately(): bool
172
    {
173
        return ! $this instanceof QueuedProjector;
174
    }
175
176
    protected function getEventStreams(StoredEvent $storedEvent): Collection
177
    {
178
        $streams = method_exists($this, 'streamEventsBy')
179
            ? $this->streamEventsBy($storedEvent)
0 ignored issues
show
Bug introduced by
It seems like streamEventsBy() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
180
            : [];
181
182
        return collect(array_wrap($streams))
183
            ->mapWithKeys(function ($streamValue, $streamName) use ($storedEvent) {
184
                if (is_numeric($streamName)) {
185
                    $streamName = $streamValue;
186
187
                    $streamValue = array_get($storedEvent->event_properties, $streamName);
188
                }
189
190
                return [$streamName => $streamValue];
191
            });
192
    }
193
194
    protected function getEventStreamFullNames(StoredEvent $storedEvent): array
195
    {
196
        $streamFullNames = $this->getEventStreams($storedEvent)
197
            ->map(function ($streamValue, $streamName) {
198
                return "{$streamName}-{$streamValue}";
199
            })
200
            ->toArray();
201
202
        if (count($streamFullNames) === 0) {
203
            $streamFullNames = ['main'];
204
        }
205
206
        return $streamFullNames;
207
    }
208
209
    protected function getStatus(string $stream = 'main'): ProjectorStatus
210
    {
211
        return ProjectorStatus::getForProjector($this, $stream);
212
    }
213
214
    protected function getAllStatuses(): Collection
215
    {
216
        return ProjectorStatus::getAllForProjector($this);
217
    }
218
}
219