Skip to content

Commit ac92007

Browse files
authored
Merge pull request #506 from Bloemendaal/dynamic-weight
feat: add dynamic weight
2 parents 5d600e3 + 67f9e77 commit ac92007

13 files changed

+180
-32
lines changed

docs/using-projectors/making-sure-events-get-handled-in-the-right-order.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,45 @@ In a local environment, where events have a very low chance of getting fired con
1717

1818
## Tweaking projector order
1919

20-
You can add a weight property to a projector to tweak the order projectors are run in. Projectors with a lower weight run first. When no explicit weight is provided, the weight is considered `0`.
20+
You can add a getWeight method to a projector to tweak the order projectors are run in. Projectors with a lower weight run first. When no explicit weight is provided, the weight is considered `0`.
2121

2222
```php
2323
namespace App\Projectors;
2424

2525
use Spatie\EventSourcing\EventHandlers\Projectors\Projector;
26+
use Spatie\EventSourcing\StoredEvents\StoredEvent;
2627

2728
class MyProjector extends Projector
2829
{
29-
public int $weight = 5;
30+
public function getWeight(?StoredEvent $event): int
31+
{
32+
return 5;
33+
}
34+
35+
//
36+
}
37+
```
38+
39+
Alternatively, you can determine the weight dynamically based on the event being processed. This allows you to prioritize certain events over others. The `$event` parameter will be `null` when the `resetState()` method is called (during projector reset operations).
40+
41+
```php
42+
namespace App\Projectors;
43+
44+
use App\Events\MoneyAddedEvent;
45+
use App\Events\MoneySubtractedEvent;
46+
use Spatie\EventSourcing\EventHandlers\Projectors\Projector;
47+
use Spatie\EventSourcing\StoredEvents\StoredEvent;
48+
49+
class MyProjector extends Projector
50+
{
51+
public function getWeight(?StoredEvent $event): int
52+
{
53+
return match ($event?->event_class) {
54+
MoneyAddedEvent::class => 2,
55+
MoneySubtractedEvent::class => -2,
56+
default => 0,
57+
};
58+
}
3059

3160
//
3261
}

docs/using-reactors/creating-and-configuring-reactors.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,14 +185,43 @@ class SendMoneyAddedMail
185185

186186
## Tweaking reactor order
187187

188-
You can add a weight property to a reactor to tweak the order reactors are run in. Reactors with a lower weight run first. When no explicit weight is provided, the weight is considered `0`.
188+
You can add a getWeight method to a reactor to tweak the order reactors are run in. Reactors with a lower weight run first. When no explicit weight is provided, the weight is considered `0`.
189189

190190
```php
191191
namespace App\Reactors;
192192

193-
class MyReactor
193+
use Spatie\EventSourcing\EventHandlers\Reactors\Reactor;
194+
use Spatie\EventSourcing\StoredEvents\StoredEvent;
195+
196+
class MyReactor extends Reactor
194197
{
195-
public int $weight = 5;
198+
public function getWeight(?StoredEvent $event): int
199+
{
200+
return 5;
201+
}
202+
}
203+
```
204+
205+
Alternatively, you can determine the weight dynamically based on the event being processed. This allows you to prioritize certain events over others.
206+
207+
```php
208+
namespace App\Reactors;
209+
210+
use App\Events\MoneyAddedEvent;
211+
use App\Events\MoneySubtractedEvent;
212+
use Spatie\EventSourcing\EventHandlers\Reactors\Reactor;
213+
use Spatie\EventSourcing\StoredEvents\StoredEvent;
214+
215+
class MyReactor extends Reactor
216+
{
217+
public function getWeight(?StoredEvent $event): int
218+
{
219+
return match ($event?->event_class) {
220+
MoneyAddedEvent::class => 2,
221+
MoneySubtractedEvent::class => -2,
222+
default => 0,
223+
};
224+
}
196225
}
197226
```
198227

src/EventHandlers/EventHandler.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ public function handle(StoredEvent $storedEvent): void;
1515
public function handleException(Exception $exception): void;
1616

1717
public function getEventHandlingMethods(): Collection;
18+
19+
public function getWeight(?StoredEvent $event): int;
1820
}

src/EventHandlers/EventHandlerCollection.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,25 @@ public function remove(array $eventHandlerClassNames): void
5151
->toArray();
5252
}
5353

54-
public function syncEventHandlers(): self
54+
public function syncEventHandlers(StoredEvent $event): self
5555
{
5656
return $this
5757
->reject(
5858
fn (EventHandler $eventHandler) => $eventHandler instanceof ShouldQueue
5959
)
6060
->sortBy(
61-
fn (EventHandler $eventHandler) => $eventHandler->weight ?? 0
61+
fn (EventHandler $eventHandler) => $eventHandler->getWeight($event)
6262
);
6363
}
6464

65-
public function asyncEventHandlers(): self
65+
public function asyncEventHandlers(StoredEvent $event): self
6666
{
6767
return $this
6868
->filter(
6969
fn (EventHandler $eventHandler) => $eventHandler instanceof ShouldQueue
7070
)
7171
->sortBy(
72-
fn (EventHandler $eventHandler) => $eventHandler->weight ?? 0
72+
fn (EventHandler $eventHandler) => $eventHandler->getWeight($event)
7373
);
7474
}
7575
}

src/EventHandlers/HandlesEvents.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,9 @@ public function getEventHandlingMethods(): Collection
5656
})
5757
->map(fn (Collection $group) => $group->map(fn (Method $method) => $method->getName())->all());
5858
}
59+
60+
public function getWeight(?StoredEvent $event): int
61+
{
62+
return $this->weight ?? 0;
63+
}
5964
}

src/Projectionist.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public function getAsyncProjectorsFor(StoredEvent $storedEvent): Collection
116116
{
117117
return $this->projectors
118118
->forEvent($storedEvent)
119-
->asyncEventHandlers();
119+
->asyncEventHandlers($storedEvent);
120120
}
121121

122122
public function addReactor($reactor): Projectionist
@@ -221,7 +221,7 @@ public function handle(StoredEvent $storedEvent): void
221221
{
222222
$projectors = $this->projectors
223223
->forEvent($storedEvent)
224-
->asyncEventHandlers();
224+
->asyncEventHandlers($storedEvent);
225225

226226
$this->applyStoredEventToProjectors(
227227
$storedEvent,
@@ -230,7 +230,7 @@ public function handle(StoredEvent $storedEvent): void
230230

231231
$reactors = $this->reactors
232232
->forEvent($storedEvent)
233-
->asyncEventHandlers();
233+
->asyncEventHandlers($storedEvent);
234234

235235
$this->applyStoredEventToReactors(
236236
$storedEvent,
@@ -242,13 +242,13 @@ public function handleWithSyncEventHandlers(StoredEvent $storedEvent): void
242242
{
243243
$projectors = $this->projectors
244244
->forEvent($storedEvent)
245-
->syncEventHandlers();
245+
->syncEventHandlers($storedEvent);
246246

247247
$this->applyStoredEventToProjectors($storedEvent, $projectors);
248248

249249
$reactors = $this->reactors
250250
->forEvent($storedEvent)
251-
->syncEventHandlers();
251+
->syncEventHandlers($storedEvent);
252252

253253
$this->applyStoredEventToReactors($storedEvent, $reactors);
254254
}
@@ -262,18 +262,18 @@ private function applyStoredEventToProjectors(StoredEvent $storedEvent, Collecti
262262
{
263263
$this->isProjecting = true;
264264

265-
foreach ($projectors as $projector) {
266-
$this->callEventHandler($projector, $storedEvent);
267-
}
265+
$projectors
266+
->sortBy(fn (EventHandler $eventHandler) => $eventHandler->getWeight($storedEvent))
267+
->each(fn (EventHandler $projector) => $this->callEventHandler($projector, $storedEvent));
268268

269269
$this->isProjecting = false;
270270
}
271271

272272
private function applyStoredEventToReactors(StoredEvent $storedEvent, Collection $reactors): void
273273
{
274-
foreach ($reactors as $reactor) {
275-
$this->callEventHandler($reactor, $storedEvent);
276-
}
274+
$reactors
275+
->sortBy(fn (EventHandler $eventHandler) => $eventHandler->getWeight($storedEvent))
276+
->each(fn (EventHandler $reactor) => $this->callEventHandler($reactor, $storedEvent));
277277
}
278278

279279
private function callEventHandler(EventHandler $eventHandler, StoredEvent $storedEvent): bool
@@ -323,7 +323,7 @@ public function replay(
323323
?string $aggregateUuid = null
324324
): void {
325325
$projectors = (new EventHandlerCollection($projectors))
326-
->sortBy(fn (EventHandler $eventHandler) => $eventHandler->weight ?? 0);
326+
->sortBy(fn (EventHandler $eventHandler) => $eventHandler->getWeight(null));
327327

328328
$this->isReplaying = true;
329329

src/StoredEvents/StoredEvent.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ protected function shouldDispatchJob(): bool
104104
/** @var \Spatie\EventSourcing\EventHandlers\EventHandlerCollection $eventHandlers */
105105
$eventHandlers = Projectionist::allEventHandlers();
106106

107-
return $eventHandlers->forEvent($this)->asyncEventHandlers()->isNotEmpty();
107+
return $eventHandlers->forEvent($this)->asyncEventHandlers($this)->isNotEmpty();
108108
}
109109

110110
protected function instantiateEvent(?ShouldBeStored $originalEvent): void

tests/ProjectionistTest.php

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Contracts\Container\BindingResolutionException;
77
use Illuminate\Support\Facades\Queue;
88

9+
use Spatie\EventSourcing\Tests\TestClasses\Projectors\ProjectorWithDynamicWeight;
910
use function PHPUnit\Framework\assertCount;
1011
use function PHPUnit\Framework\assertEquals;
1112
use function PHPUnit\Framework\assertInstanceOf;
@@ -66,30 +67,42 @@
6667
assertEquals(1, ProjectorThatThrowsAnException::$exceptionsHandled);
6768
});
6869

69-
it('will call projectors ordered by weight', function () {
70+
it('will call projectors ordered by weight', function (string $eventClass, array $sortedProjectors) {
7071
app()->singleton(ProjectorWithWeightTestHelper::class);
7172

73+
Projectionist::addProjector(ProjectorWithDynamicWeight::class);
7274
Projectionist::addProjector(ProjectorWithHighWeight::class);
7375
Projectionist::addProjector(ProjectorWithoutWeight::class);
7476
Projectionist::addProjector(ProjectorWithNegativeWeight::class);
7577
Projectionist::addProjector(ProjectorWithLowWeight::class);
7678

77-
event(new MoneyAddedEvent($this->account, 1000));
79+
event(new $eventClass($this->account, 1000));
7880

79-
assertSame([
81+
assertSame($sortedProjectors, app(ProjectorWithWeightTestHelper::class)->calledBy);
82+
})->with([
83+
[MoneyAddedEvent::class, [
8084
ProjectorWithNegativeWeight::class,
8185
ProjectorWithoutWeight::class,
8286
ProjectorWithLowWeight::class,
87+
ProjectorWithDynamicWeight::class,
8388
ProjectorWithHighWeight::class,
84-
], app(ProjectorWithWeightTestHelper::class)->calledBy);
85-
});
89+
]],
90+
[MoneySubtractedEvent::class, [
91+
ProjectorWithNegativeWeight::class,
92+
ProjectorWithDynamicWeight::class,
93+
ProjectorWithoutWeight::class,
94+
ProjectorWithLowWeight::class,
95+
ProjectorWithHighWeight::class,
96+
]],
97+
]);
8698

87-
it('will replay projectors ordered by weight', function () {
99+
it('will replay projectors ordered by weight', function (string $eventClass, array $sortedProjectors) {
88100
app()->singleton(ProjectorWithWeightTestHelper::class);
89101

90-
event(new MoneyAddedEvent($this->account, 1000));
102+
event(new $eventClass($this->account, 1000));
91103

92104
Projectionist::addProjectors([
105+
ProjectorWithDynamicWeight::class,
93106
ProjectorWithHighWeight::class,
94107
ProjectorWithoutWeight::class,
95108
ProjectorWithNegativeWeight::class,
@@ -98,13 +111,23 @@
98111

99112
Projectionist::replay(Projectionist::getProjectors());
100113

101-
assertSame([
114+
assertSame($sortedProjectors, app(ProjectorWithWeightTestHelper::class)->calledBy);
115+
})->with([
116+
[MoneyAddedEvent::class, [
102117
ProjectorWithNegativeWeight::class,
103118
ProjectorWithoutWeight::class,
104119
ProjectorWithLowWeight::class,
120+
ProjectorWithDynamicWeight::class,
105121
ProjectorWithHighWeight::class,
106-
], app(ProjectorWithWeightTestHelper::class)->calledBy);
107-
});
122+
]],
123+
[MoneySubtractedEvent::class, [
124+
ProjectorWithNegativeWeight::class,
125+
ProjectorWithDynamicWeight::class,
126+
ProjectorWithoutWeight::class,
127+
ProjectorWithLowWeight::class,
128+
ProjectorWithHighWeight::class,
129+
]],
130+
]);
108131

109132
it('can catch exceptions and still continue calling other projectors', function () {
110133
$this->setConfig('event-sourcing.catch_exceptions', true);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Spatie\EventSourcing\Tests\TestClasses\Projectors;
4+
5+
use Spatie\EventSourcing\EventHandlers\Projectors\Projector;
6+
use Spatie\EventSourcing\StoredEvents\StoredEvent;
7+
use Spatie\EventSourcing\Tests\TestClasses\Events\MoneyAddedEvent;
8+
use Spatie\EventSourcing\Tests\TestClasses\Events\MoneySubtractedEvent;
9+
use Spatie\EventSourcing\Tests\TestClasses\ProjectorWithWeightTestHelper;
10+
11+
class ProjectorWithDynamicWeight extends Projector
12+
{
13+
public function __construct(
14+
private ProjectorWithWeightTestHelper $projectorWithWeightTestHelper,
15+
) {
16+
}
17+
18+
public function getWeight(?StoredEvent $event): int
19+
{
20+
return match ($event?->event_class) {
21+
MoneyAddedEvent::class => 2,
22+
MoneySubtractedEvent::class => -2,
23+
default => 0,
24+
};
25+
}
26+
27+
public function onMoneyAdded(MoneyAddedEvent $event): void
28+
{
29+
$this->projectorWithWeightTestHelper->calledBy(static::class);
30+
}
31+
32+
public function onMoneySubtracted(MoneySubtractedEvent $event): void
33+
{
34+
$this->projectorWithWeightTestHelper->calledBy(static::class);
35+
}
36+
}

tests/TestClasses/Projectors/ProjectorWithHighWeight.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Spatie\EventSourcing\EventHandlers\Projectors\Projector;
66
use Spatie\EventSourcing\Tests\TestClasses\Events\MoneyAddedEvent;
7+
use Spatie\EventSourcing\Tests\TestClasses\Events\MoneySubtractedEvent;
78
use Spatie\EventSourcing\Tests\TestClasses\ProjectorWithWeightTestHelper;
89

910
class ProjectorWithHighWeight extends Projector
@@ -19,4 +20,9 @@ public function onMoneyAdded(MoneyAddedEvent $event): void
1920
{
2021
$this->projectorWithWeightTestHelper->calledBy(static::class);
2122
}
23+
24+
public function onMoneySubtracted(MoneySubtractedEvent $event): void
25+
{
26+
$this->projectorWithWeightTestHelper->calledBy(static::class);
27+
}
2228
}

0 commit comments

Comments
 (0)