diff --git a/Tests/DispatcherTest.php b/Tests/DispatcherTest.php index 8776489f..96ed63c8 100644 --- a/Tests/DispatcherTest.php +++ b/Tests/DispatcherTest.php @@ -11,6 +11,7 @@ use Joomla\Event\EventInterface; use Joomla\Event\EventImmutable; use Joomla\Event\Priority; +use Joomla\Event\Tests\Stubs\ErrorResistibleEvent; use Joomla\Event\Tests\Stubs\FirstListener; use Joomla\Event\Tests\Stubs\SecondListener; use Joomla\Event\Tests\Stubs\SomethingListener; @@ -717,4 +718,51 @@ public function testRemoveSubscriber() $this->assertFalse($this->instance->hasListener([$listener, 'onSomething'])); $this->assertFalse($this->instance->hasListener([$listener, 'onAfterSomething'])); } + + /** + * @testdox An error resistible event works as expected the dispatcher + * + * @covers \Joomla\Event\Dispatcher + * @uses \Joomla\Event\ErrorResistibleEventInterface + */ + public function testErrorResistibleEvent() + { + $this->instance->addListener('onErroredEventTest', function () { + throw new \Exception('Event error 1'); + }); + $this->instance->addListener('onErroredEventTest', function () { + // No error + }); + $this->instance->addListener('onErroredEventTest', function () { + throw new \Exception('Event error 2'); + }); + + $errors = []; + $event = new ErrorResistibleEvent('onErroredEventTest', [], function (\Throwable $e) use (&$errors) { + $errors[] = $e->getMessage(); + }); + + $this->instance->dispatch('onErroredEventTest', $event); + + $this->assertEquals(2, count($errors), 'The error handler of the event should collect correct amount of errors.'); + } + + /** + * @testdox An error resistible event throws an TypeError when error handler not set + * + * @covers \Joomla\Event\Dispatcher + * @uses \Joomla\Event\ErrorResistibleEventInterface + */ + public function testErrorResistibleEventIncorectlyImplemented() + { + $this->instance->addListener('onErroredEventTest', function () { + throw new \Exception('Event error 1'); + }); + + $event = new ErrorResistibleEvent('onErroredEventTest'); + + $this->expectException(\TypeError::class); + + $this->instance->dispatch('onErroredEventTest', $event); + } } diff --git a/Tests/Stubs/ErrorResistibleEvent.php b/Tests/Stubs/ErrorResistibleEvent.php new file mode 100644 index 00000000..334ff54b --- /dev/null +++ b/Tests/Stubs/ErrorResistibleEvent.php @@ -0,0 +1,37 @@ +errorHandler = $errorHandler; + } +} diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 70496f9d..b67d7123 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -445,7 +445,23 @@ public function dispatch(string $name, ?EventInterface $event = null): EventInte $event = $this->getDefaultEvent($name); } - if (isset($this->listeners[$event->getName()])) { + if (!isset($this->listeners[$event->getName()])) { + return $event; + } + + if ($event instanceof ErrorResistibleEventInterface) { + foreach ($this->listeners[$event->getName()] as $listener) { + if ($event->isStopped()) { + return $event; + } + + try { + $listener($event); + } catch (\Throwable $e) { + $event->handleError($e); + } + } + } else { foreach ($this->listeners[$event->getName()] as $listener) { if ($event->isStopped()) { return $event; diff --git a/src/ErrorResistibleEventInterface.php b/src/ErrorResistibleEventInterface.php new file mode 100644 index 00000000..f3cbf688 --- /dev/null +++ b/src/ErrorResistibleEventInterface.php @@ -0,0 +1,37 @@ +errorHandler; + } + + /** + * Handle the error. + * + * @param \Throwable $error The error instance + * + * @since __DEPLOY_VERSION__ + */ + public function handleError(\Throwable $error): void + { + $handler = $this->getErrorHandler(); + $handler($error); + } +}