Skip to content

Commit 5769ec2

Browse files
authored
feat(eventbus): add #[StopsPropagation] (#1740)
1 parent 908352a commit 5769ec2

File tree

8 files changed

+135
-0
lines changed

8 files changed

+135
-0
lines changed

docs/2-features/08-events.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,31 @@ final readonly class EventLoggerMiddleware implements EventBusMiddleware
159159
{ /* … */ }
160160
```
161161

162+
## Stopping event propagation
163+
164+
In rare cases you might want an event only to be handled by a single handler. You can use the `b{Tempest\EventBus\StopsPropagation}` attribute on both events and event handlers to achieve this:
165+
166+
```php
167+
use Tempest\EventBus\StopsPropagation;
168+
169+
#[StopsPropagation]
170+
final class MyEvent {}
171+
```
172+
173+
```php
174+
use Tempest\EventBus\StopsPropagation;
175+
use Tempest\EventBus\EventHandler;
176+
177+
final class MyHandler
178+
{
179+
#[StopsPropagation]
180+
public function handle(OtherEvent $event): void
181+
{
182+
// …
183+
}
184+
}
185+
```
186+
162187
## Built-in framework events
163188

164189
Tempest includes a few built-in events that are primarily used internally. While most applications won’t need them, you are free to listen to them if desired.

packages/event-bus/src/GenericEventBus.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use Tempest\Support\Str;
1010
use UnitEnum;
1111

12+
use function Tempest\reflect;
13+
1214
final readonly class GenericEventBus implements EventBus
1315
{
1416
public function __construct(
@@ -55,13 +57,21 @@ private function resolveHandlers(string|object $event): array
5557
return $handlers;
5658
}
5759

60+
/** @param \Tempest\EventBus\CallableEventHandler[] $eventHandlers */
5861
private function getCallable(array $eventHandlers): EventBusMiddlewareCallable
5962
{
6063
$callable = new EventBusMiddlewareCallable(function (string|object $event) use ($eventHandlers): void {
6164
foreach ($eventHandlers as $eventHandler) {
6265
$callable = $eventHandler->normalizeCallable($this->container);
6366

6467
$callable($event);
68+
69+
if (
70+
is_object($event) && reflect($event)->hasAttribute(StopsPropagation::class)
71+
|| ($eventHandler->handler->handler ?? null)?->hasAttribute(StopsPropagation::class)
72+
) {
73+
break;
74+
}
6575
}
6676
});
6777

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Tempest\EventBus;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
8+
final readonly class StopsPropagation
9+
{
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Fixtures\Events;
4+
5+
final readonly class EventForListenerWithoutPropagation
6+
{
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Fixtures\Events;
4+
5+
use Tempest\EventBus\StopsPropagation;
6+
7+
#[StopsPropagation]
8+
final readonly class EventWithoutPropagation
9+
{
10+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Fixtures\Events;
4+
5+
use Tempest\Container\Singleton;
6+
use Tempest\EventBus\EventHandler;
7+
use Tempest\EventBus\StopsPropagation;
8+
9+
#[Singleton]
10+
final class HandlersForEventWithListenerWithoutPropagation
11+
{
12+
public int $count = 0;
13+
14+
#[EventHandler, StopsPropagation]
15+
public function a(EventForListenerWithoutPropagation $event): void
16+
{
17+
$this->count++;
18+
}
19+
20+
#[EventHandler]
21+
public function b(EventForListenerWithoutPropagation $event): void
22+
{
23+
$this->count++;
24+
}
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Tests\Tempest\Fixtures\Events;
4+
5+
use Tempest\Container\Singleton;
6+
use Tempest\EventBus\EventHandler;
7+
8+
#[Singleton]
9+
final class HandlersForEventWithoutPropagation
10+
{
11+
public int $count = 0;
12+
13+
#[EventHandler]
14+
public function a(EventWithoutPropagation $event): void
15+
{
16+
$this->count++;
17+
}
18+
19+
#[EventHandler]
20+
public function b(EventWithoutPropagation $event): void
21+
{
22+
$this->count++;
23+
}
24+
}

tests/Integration/EventBus/EventBusTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
use Tests\Tempest\Fixtures\Events\DiscoveredEventBusMiddleware;
88
use Tests\Tempest\Fixtures\Events\EnumEvent;
9+
use Tests\Tempest\Fixtures\Events\EventForListenerWithoutPropagation;
910
use Tests\Tempest\Fixtures\Events\EventInterfaceImplementation;
11+
use Tests\Tempest\Fixtures\Events\EventWithoutPropagation;
12+
use Tests\Tempest\Fixtures\Events\HandlersForEventWithListenerWithoutPropagation;
13+
use Tests\Tempest\Fixtures\Events\HandlersForEventWithoutPropagation;
1014
use Tests\Tempest\Fixtures\Events\OtherEnumEvent;
1115
use Tests\Tempest\Fixtures\Events\TestEventHandler;
1216
use Tests\Tempest\Fixtures\Handlers\EventInterfaceHandler;
@@ -63,4 +67,24 @@ public function test_discovered_middleware(): void
6367

6468
$this->assertTrue(DiscoveredEventBusMiddleware::$hit);
6569
}
70+
71+
public function test_event_without_propagation(): void
72+
{
73+
$handler = $this->get(HandlersForEventWithoutPropagation::class);
74+
$handler->count = 0;
75+
76+
event(new EventWithoutPropagation());
77+
78+
$this->assertSame(1, $handler->count);
79+
}
80+
81+
public function test_listener_without_propagation(): void
82+
{
83+
$handler = $this->get(HandlersForEventWithListenerWithoutPropagation::class);
84+
$handler->count = 0;
85+
86+
event(new EventForListenerWithoutPropagation());
87+
88+
$this->assertSame(1, $handler->count);
89+
}
6690
}

0 commit comments

Comments
 (0)