diff --git a/Tests/DispatcherTest.php b/Tests/DispatcherTest.php index 8776489f..50894599 100644 --- a/Tests/DispatcherTest.php +++ b/Tests/DispatcherTest.php @@ -717,4 +717,69 @@ public function testRemoveSubscriber() $this->assertFalse($this->instance->hasListener([$listener, 'onSomething'])); $this->assertFalse($this->instance->hasListener([$listener, 'onAfterSomething'])); } + + /** + * @testdox The dispatcher should throw an error when error handler not set + * + * @covers Joomla\Event\Dispatcher + * @uses Joomla\Event\DispatcherWithErrorHandlerInterface + */ + public function testDispatcherWithErrorHandlerNotSet() + { + $event = new Event('onErroredEventTest'); + + $this->instance->addListener('onErroredEventTest', function () { + throw new \RuntimeException('Event error 1'); + }); + + $this->expectException(\RuntimeException::class); + + $this->instance->dispatch('onErroredEventTest', $event); + } + + /** + * @testdox The dispatchers error handler should handle an errors + * + * @covers Joomla\Event\Dispatcher + * @uses Joomla\Event\DispatcherWithErrorHandlerInterface + */ + public function testDispatcherWithErrorHandlerAreSet() + { + $event = new Event('onErroredEventTest'); + $errors = []; + + $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'); + }); + + $this->instance->setErrorHandler(function (\Throwable $e) use (&$errors) { + $errors[] = $e; + }); + + $this->instance->dispatch('onErroredEventTest', $event); + + $this->assertEquals(2, count($errors), 'The error handler should collect correct amount of errors.'); + } + + /** + * @testdox The dispatcher should return previous Error handler when new is set + * + * @covers Joomla\Event\Dispatcher + * @uses Joomla\Event\DispatcherWithErrorHandlerInterface + */ + public function testDispatcherWithErrorHandlerReturnPrevious() + { + $prev1 = $this->instance->setErrorHandler('var_dump'); + $prev2 = $this->instance->setErrorHandler('print_r'); + $prev3 = $this->instance->setErrorHandler(null); + + $this->assertEquals([null, 'var_dump', 'print_r'], [$prev1, $prev2, $prev3], 'The dispatcher should return previous Error handler'); + } + } diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 70496f9d..2c4e75a0 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -14,7 +14,7 @@ * * @since 1.0 */ -class Dispatcher implements DispatcherInterface +class Dispatcher implements DispatcherInterface, DispatcherWithErrorHandlerInterface { /** * An array of registered events indexed by the event names. @@ -33,6 +33,15 @@ class Dispatcher implements DispatcherInterface */ protected $listeners = []; + /** + * Error handler. + * + * @return callable + * + * @since __DEPLOY_VERSION__ + */ + protected $errorHandler; + /** * Set an event to the dispatcher. It will replace any event with the same name. * @@ -451,7 +460,11 @@ public function dispatch(string $name, ?EventInterface $event = null): EventInte return $event; } - $listener($event); + try { + $listener($event); + } catch (\Throwable $e) { + $this->handleError($e, $event); + } } } @@ -503,4 +516,41 @@ private function getDefaultEvent(string $name): EventInterface return new Event($name); } + + /** + * Set error handler for the dispatcher to handler errors in an event listeners. + * + * @param ?callable $handler The error handler + * + * @return ?callable Previous error handler + * + * @since __DEPLOY_VERSION__ + */ + public function setErrorHandler(?callable $handler): ?callable + { + $previous = $this->errorHandler; + + $this->errorHandler = $handler; + + return $previous; + } + + /** + * Handle the error. Or throw it when no handler were set. + * + * @param \Throwable $error The error instance + * @param EventInterface $event The event which were dispatched + * + * @since __DEPLOY_VERSION__ + * + * @throws \Throwable + */ + protected function handleError(\Throwable $error, EventInterface $event): void + { + if (!$this->errorHandler) { + throw $error; + } + + call_user_func($this->errorHandler, $error, $event); + } } diff --git a/src/DispatcherWithErrorHandlerInterface.php b/src/DispatcherWithErrorHandlerInterface.php new file mode 100644 index 00000000..7e3653d1 --- /dev/null +++ b/src/DispatcherWithErrorHandlerInterface.php @@ -0,0 +1,29 @@ +