Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions Tests/DispatcherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

}
54 changes: 52 additions & 2 deletions src/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
*
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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);
}
}
29 changes: 29 additions & 0 deletions src/DispatcherWithErrorHandlerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/**
* Part of the Joomla Framework Event Package
*
* @copyright Copyright (C) 2005 - 2023 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/

namespace Joomla\Event;

/**
* Interface for dispatcher with error handler.
*
* @since __DEPLOY_VERSION__
*/
interface DispatcherWithErrorHandlerInterface
{
/**
* 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;
}