Skip to content

Commit bf7ff15

Browse files
authored
feat: event bus improvements (#623)
1 parent 803780e commit bf7ff15

File tree

13 files changed

+201
-15
lines changed

13 files changed

+201
-15
lines changed

src/Tempest/EventBus/src/EventBusDiscovery.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Tempest\Core\HandlesDiscoveryCache;
1111
use Tempest\Reflection\ClassReflector;
1212
use Tempest\Reflection\MethodReflector;
13+
use Tempest\Reflection\TypeReflector;
1314
use UnitEnum;
1415

1516
final readonly class EventBusDiscovery implements Discovery
@@ -44,9 +45,10 @@ public function discover(ClassReflector $class): void
4445
continue;
4546
}
4647

48+
/** @var TypeReflector $type */
4749
$type = $parameters[0]->getType();
4850

49-
if (! $type->isClass()) {
51+
if (! $type->isClass() && ! $type->isInterface()) {
5052
continue;
5153
}
5254

src/Tempest/EventBus/src/GenericEventBus.php

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,44 @@ public function listen(string|object $event, Closure $handler): void
2424

2525
public function dispatch(string|object $event): void
2626
{
27-
$eventName = match(true) {
27+
28+
$eventHandlers = $this->resolveHandlers($event);
29+
30+
$dispatch = $this->applyMiddleware(function (string|object $event) use ($eventHandlers): void {
31+
foreach ($eventHandlers as $eventHandler) {
32+
$callable = $eventHandler->normalizeCallable($this->container);
33+
34+
$callable($event);
35+
}
36+
});
37+
38+
$dispatch($event);
39+
}
40+
41+
/** @return \Tempest\EventBus\CallableEventHandler[] */
42+
private function resolveHandlers(string|object $event): array
43+
{
44+
$eventName = match (true) {
2845
$event instanceof BackedEnum => $event->value,
2946
$event instanceof UnitEnum => $event->name,
3047
is_string($event) => $event,
3148
default => $event::class,
3249
};
3350

34-
/** @var \Tempest\EventBus\CallableEventHandler[] $eventHandlers */
35-
$eventHandlers = $this->eventBusConfig->handlers[$eventName] ?? [];
51+
$handlers = $this->eventBusConfig->handlers[$eventName] ?? [];
3652

37-
foreach ($eventHandlers as $eventHandler) {
38-
$callable = $eventHandler->normalizeCallable($this->container);
39-
$callable = $this->applyMiddleware($callable);
53+
if (is_object($event)) {
54+
$interfaces = class_implements($event);
4055

41-
$callable($event);
56+
foreach ($interfaces as $interface) {
57+
$handlers = [
58+
...$handlers,
59+
...($this->eventBusConfig->handlers[$interface] ?? []),
60+
];
61+
}
4262
}
63+
64+
return $handlers;
4365
}
4466

4567
private function applyMiddleware(Closure $handler): Closure
@@ -50,7 +72,7 @@ private function applyMiddleware(Closure $handler): Closure
5072
$handler = fn (string|object $event) => $this->container->invoke(
5173
$middlewareClass,
5274
event: $event,
53-
next: $handler
75+
next: $handler,
5476
);
5577
}
5678

src/Tempest/EventBus/tests/EventBusTest.php

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
use Tempest\EventBus\EventBusConfig;
1313
use Tempest\EventBus\EventHandler;
1414
use Tempest\EventBus\GenericEventBus;
15+
use Tempest\EventBus\Tests\Fixtures\EventInterface;
16+
use Tempest\EventBus\Tests\Fixtures\EventInterfaceHandler;
17+
use Tempest\EventBus\Tests\Fixtures\EventInterfaceImplementation;
1518
use Tempest\EventBus\Tests\Fixtures\ItHappened;
1619
use Tempest\EventBus\Tests\Fixtures\MyEventBusMiddleware;
1720
use Tempest\EventBus\Tests\Fixtures\MyEventHandler;
@@ -46,12 +49,40 @@ public function test_class_based_handlers(): void
4649
$eventBus = new GenericEventBus($container, $config);
4750

4851
MyEventHandler::$itHappened = false;
49-
MyEventBusMiddleware::$hit = false;
52+
MyEventBusMiddleware::$hits = 0;
5053

5154
$eventBus->dispatch(new ItHappened());
5255

5356
$this->assertTrue(MyEventHandler::$itHappened);
54-
$this->assertTrue(MyEventBusMiddleware::$hit);
57+
$this->assertSame(1, MyEventBusMiddleware::$hits);
58+
}
59+
60+
public function test_middleware_is_only_triggered_once_per_event_dispatch(): void
61+
{
62+
$container = new GenericContainer();
63+
64+
$handler = new EventHandler();
65+
$handler->setHandler(new MethodReflector(new ReflectionMethod(MyEventHandler::class, 'handleItHappened')));
66+
67+
$config = new EventBusConfig(
68+
handlers: [
69+
ItHappened::class => [
70+
new CallableEventHandler(ItHappened::class, $handler),
71+
new CallableEventHandler(ItHappened::class, $handler),
72+
],
73+
],
74+
middleware: [
75+
MyEventBusMiddleware::class,
76+
]
77+
);
78+
79+
$eventBus = new GenericEventBus($container, $config);
80+
81+
MyEventBusMiddleware::$hits = 0;
82+
83+
$eventBus->dispatch(new ItHappened());
84+
85+
$this->assertSame(1, MyEventBusMiddleware::$hits);
5586
}
5687

5788
public function test_closure_based_handlers(): void
@@ -76,12 +107,12 @@ public function test_closure_based_handlers(): void
76107

77108
$eventBus = new GenericEventBus($container, $config);
78109

79-
MyEventBusMiddleware::$hit = false;
110+
MyEventBusMiddleware::$hits = 0;
80111

81112
$eventBus->dispatch(new ItHappened());
82113

83114
$this->assertSame('bar', $called);
84-
$this->assertTrue(MyEventBusMiddleware::$hit);
115+
$this->assertSame(1, MyEventBusMiddleware::$hits);
85116
}
86117

87118
public function test_closure_based_handlers_using_listen_method(): void
@@ -117,4 +148,28 @@ public function test_closure_based_handlers_using_function(): void
117148

118149
$this->assertTrue($hasHappened);
119150
}
151+
152+
public function test_interface_handlers(): void
153+
{
154+
$container = new GenericContainer();
155+
156+
$handler = new EventHandler();
157+
$handler->setHandler(new MethodReflector(new ReflectionMethod(EventInterfaceHandler::class, 'handleItHappened')));
158+
159+
$config = new EventBusConfig(
160+
handlers: [
161+
EventInterface::class => [
162+
new CallableEventHandler(EventInterface::class, $handler),
163+
],
164+
]
165+
);
166+
167+
$eventBus = new GenericEventBus($container, $config);
168+
169+
EventInterfaceHandler::$itHappened = false;
170+
171+
$eventBus->dispatch(new EventInterfaceImplementation());
172+
173+
$this->assertTrue(EventInterfaceHandler::$itHappened);
174+
}
120175
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\EventBus\Tests\Fixtures;
6+
7+
interface EventInterface
8+
{
9+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\EventBus\Tests\Fixtures;
6+
7+
use Tempest\EventBus\EventHandler;
8+
9+
final class EventInterfaceHandler
10+
{
11+
public static bool $itHappened = false;
12+
13+
#[EventHandler]
14+
public function handleItHappened(EventInterface $event): void
15+
{
16+
self::$itHappened = true;
17+
}
18+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\EventBus\Tests\Fixtures;
6+
7+
final readonly class EventInterfaceImplementation implements EventInterface
8+
{
9+
}

src/Tempest/EventBus/tests/Fixtures/MyEventBusMiddleware.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
final class MyEventBusMiddleware implements EventBusMiddleware
1010
{
11-
public static bool $hit = false;
11+
public static int $hits = 0;
1212

1313
public function __invoke(object $event, callable $next): void
1414
{
15-
self::$hit = true;
15+
self::$hits += 1;
1616

1717
$next($event);
1818
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\EventBus\Tests\Fixtures;
6+
7+
use Tempest\EventBus\EventHandler;
8+
9+
final class MyInterfaceEventHandler
10+
{
11+
public static bool $itHappened = false;
12+
13+
#[EventHandler]
14+
public function handleItHappened(ItHappened $event): void
15+
{
16+
self::$itHappened = true;
17+
}
18+
}

src/Tempest/Reflection/src/TypeReflector.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ public function isClass(): bool
144144
return class_exists($this->cleanDefinition);
145145
}
146146

147+
public function isInterface(): bool
148+
{
149+
return interface_exists($this->cleanDefinition);
150+
}
151+
147152
public function isIterable(): bool
148153
{
149154
return in_array($this->cleanDefinition, [
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Fixtures\Events;
6+
7+
interface EventInterface
8+
{
9+
}

0 commit comments

Comments
 (0)