Skip to content

Commit 3a87027

Browse files
author
Maxime Rainville
committed
Add better error handling
1 parent e33f638 commit 3a87027

File tree

3 files changed

+48
-17
lines changed

3 files changed

+48
-17
lines changed

examples/basic-usage.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,11 @@ function () use ($event) {
6060
$event = new UserCreatedEvent('789', '[email protected]');
6161
$future = $dispatcher->dispatch($event, new TimeoutCancellation(30));
6262
EventLoop::run();
63+
64+
// Set up logging for your dispatcher - all errors will be logged to PSR logger
65+
$dispatcher = new AsyncEventDispatcher(
66+
$listenerProvider,
67+
function (Throwable $exception) {
68+
error_log($exception->getMessage());
69+
}
70+
);

src/AsyncEventDispatcher.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@
77
use function Amp\async;
88

99
use Amp\Cancellation;
10+
1011
use Amp\Future;
1112

1213
use function Amp\Future\awaitAll;
1314

1415
use Amp\NullCancellation;
16+
use Closure;
1517
use Psr\EventDispatcher\EventDispatcherInterface;
1618
use Psr\EventDispatcher\ListenerProviderInterface;
1719
use Psr\EventDispatcher\StoppableEventInterface;
20+
use Throwable;
1821

1922
/**
2023
* Asynchronous implementation of PSR-14 EventDispatcherInterface using Revolt and AMPHP.
@@ -24,12 +27,22 @@
2427
*/
2528
class AsyncEventDispatcher implements EventDispatcherInterface
2629
{
30+
/** @var Closure(Throwable): (void) */
31+
private Closure $errorHandler;
32+
2733
/**
2834
* @param ListenerProviderInterface $listenerProvider The provider of event listeners
35+
* @param Closure(Throwable): (void) $errorHandler The handler for errors thrown by listeners
2936
*/
3037
public function __construct(
31-
private readonly ListenerProviderInterface $listenerProvider
38+
private readonly ListenerProviderInterface $listenerProvider,
39+
?Closure $errorHandler = null
3240
) {
41+
if ($errorHandler === null) {
42+
$this->errorHandler = function (Throwable $exception): void {};
43+
} else {
44+
$this->errorHandler = $errorHandler;
45+
}
3346
}
3447

3548
/**
@@ -85,7 +98,7 @@ private function dispatchStoppableEvent(
8598
// that doesn't mean we want to block other listeners outside this loop.
8699
$future = async(function () use ($event, $listener) {
87100
$listener($event);
88-
});
101+
})->catch($this->errorHandler);
89102

90103
$future->await($cancellation);
91104

@@ -120,14 +133,13 @@ private function dispatchNonStoppableEvent(
120133
foreach ($listeners as $listener) {
121134
$futures[] = async(function () use ($event, $listener) {
122135
$listener($event);
123-
});
136+
})->catch($this->errorHandler);
124137
}
125138

126-
// Wait for all listeners to complete
139+
// Wait for all listeners to complete. This will carry on despite errors.
127140
awaitAll($futures, $cancellation);
128141

129142
return $event;
130143
});
131144
}
132-
133145
}

tests/AsyncEventDispatcherTest.php

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
use ArchiPro\EventDispatcher\Tests\Fixture\TestEvent;
1616
use Exception;
1717
use PHPUnit\Framework\TestCase;
18-
use Revolt\EventLoop;
19-
2018
use Throwable;
2119

2220
/**
@@ -34,18 +32,21 @@ class AsyncEventDispatcherTest extends TestCase
3432
private ListenerProvider $listenerProvider;
3533
private AsyncEventDispatcher $dispatcher;
3634

35+
/** @var array<Throwable> */
36+
private array $errors = [];
37+
3738
/**
3839
* Sets up the test environment before each test.
3940
*/
4041
protected function setUp(): void
4142
{
4243
$this->listenerProvider = new ListenerProvider();
43-
$this->dispatcher = new AsyncEventDispatcher($this->listenerProvider);
44-
45-
EventLoop::setErrorHandler(function (Throwable $err) {
46-
throw $err;
47-
});
48-
44+
$this->dispatcher = new AsyncEventDispatcher(
45+
$this->listenerProvider,
46+
function (Throwable $exception) {
47+
$this->errors[] = $exception;
48+
}
49+
);
4950
}
5051

5152
/**
@@ -80,6 +81,8 @@ public function testDispatchEventToMultipleListeners(): void
8081
$this->assertCount(2, $results);
8182
$this->assertContains('listener1: test data', $results);
8283
$this->assertContains('listener2: test data', $results);
84+
85+
$this->assertCount(0, $this->errors, 'No errors are logged');
8386
}
8487

8588
/**
@@ -103,6 +106,8 @@ public function testSynchronousStoppableEvent(): void
103106

104107
$this->assertCount(1, $results);
105108
$this->assertEquals(['listener1'], $results);
109+
110+
$this->assertCount(0, $this->errors, 'No errors are logged');
106111
}
107112

108113
/**
@@ -114,6 +119,7 @@ public function testNoListenersForEvent(): void
114119
$dispatchedEvent = $this->dispatcher->dispatch($event);
115120

116121
$this->assertSame($event, $dispatchedEvent->await());
122+
$this->assertCount(0, $this->errors, 'No errors are logged');
117123
}
118124

119125
/**
@@ -158,16 +164,18 @@ public function testDispatchesFailureInOneListenerDoesNotAffectOthers(): void
158164

159165
$futureEvent = $this->dispatcher->dispatch($event);
160166

161-
$futureEvent = $futureEvent->await();
167+
$futureEvent->await();
162168

163169
$this->assertTrue(
164-
$futureEvent->calledOnce,
170+
$event->calledOnce,
165171
'The first listener should have been called'
166172
);
167173
$this->assertTrue(
168-
$futureEvent->calledTwice,
174+
$event->calledTwice,
169175
'The second listener should have been called despite the failure of the first listener'
170176
);
177+
178+
$this->assertCount(2, $this->errors, 'Errors are caught for both listeners');
171179
}
172180

173181
public function testCancellationOfStoppableEvent(): void
@@ -187,6 +195,8 @@ public function testCancellationOfStoppableEvent(): void
187195
$this->expectException(CancelledException::class);
188196

189197
$this->dispatcher->dispatch($event, $cancellation)->await();
198+
199+
$this->assertCount(0, $this->errors, 'No errors are caught');
190200
}
191201

192202
public function testCancellationOfNonStoppableEvent(): void
@@ -206,6 +216,7 @@ public function testCancellationOfNonStoppableEvent(): void
206216
$this->expectException(CancelledException::class);
207217

208218
$this->dispatcher->dispatch($event, $cancellation)->await();
209-
}
210219

220+
$this->assertCount(0, $this->errors, 'No errors are caught');
221+
}
211222
}

0 commit comments

Comments
 (0)