From 1128b51c47b942055752d5ea7cb7aa16f26fe315 Mon Sep 17 00:00:00 2001 From: Fedik Date: Thu, 20 Jul 2023 17:57:31 +0300 Subject: [PATCH 1/6] Error Resistible Event --- Tests/DispatcherTest.php | 26 ++++++++++++++ Tests/Stubs/ErrorResistibleEvent.php | 51 +++++++++++++++++++++++++++ src/Dispatcher.php | 18 +++++++++- src/ErrorResistibleEventInterface.php | 39 ++++++++++++++++++++ 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 Tests/Stubs/ErrorResistibleEvent.php create mode 100644 src/ErrorResistibleEventInterface.php diff --git a/Tests/DispatcherTest.php b/Tests/DispatcherTest.php index 8776489f..b83e6c65 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,29 @@ 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'); + }); + + $event = new ErrorResistibleEvent('onErroredEventTest'); + + $this->instance->dispatch('onErroredEventTest', $event); + + $this->assertEquals(3, count($event->getErrors()), 'The event should collect correct amount of errors.'); + } } diff --git a/Tests/Stubs/ErrorResistibleEvent.php b/Tests/Stubs/ErrorResistibleEvent.php new file mode 100644 index 00000000..38bdf3f9 --- /dev/null +++ b/Tests/Stubs/ErrorResistibleEvent.php @@ -0,0 +1,51 @@ +errors[] = $error; + } + + /** + * Get list of errors that happened during dispatching of the event. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getErrors(): array + { + return $this->errors; + } +} diff --git a/src/Dispatcher.php b/src/Dispatcher.php index fbfef486..07b2ee96 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -476,6 +476,8 @@ public function dispatch(string $name, ?EventInterface $event = null): EventInte if (isset($this->listeners[$event->getName()])) { + $errorResistible = $event instanceof ErrorResistibleEventInterface; + foreach ($this->listeners[$event->getName()] as $listener) { if ($event->isStopped()) @@ -483,7 +485,21 @@ public function dispatch(string $name, ?EventInterface $event = null): EventInte return $event; } - $listener($event); + if (!$errorResistible) + { + $listener($event); + } + else + { + try + { + $listener($event); + } + catch (\Throwable $e) + { + $event->addError($e); + } + } } } diff --git a/src/ErrorResistibleEventInterface.php b/src/ErrorResistibleEventInterface.php new file mode 100644 index 00000000..42835bc5 --- /dev/null +++ b/src/ErrorResistibleEventInterface.php @@ -0,0 +1,39 @@ + Date: Thu, 20 Jul 2023 18:01:50 +0300 Subject: [PATCH 2/6] Error Resistible Event --- src/ErrorResistibleEventInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ErrorResistibleEventInterface.php b/src/ErrorResistibleEventInterface.php index 42835bc5..0ac32750 100644 --- a/src/ErrorResistibleEventInterface.php +++ b/src/ErrorResistibleEventInterface.php @@ -10,8 +10,8 @@ /** * Interface for error resistible events. - * Event does not crash dispatching when exception happened, - * the exception is collected, and dispatching continuous. + * Event do not crash the dispatching process when an exception happening, + * the exception is collected, and the dispatching process is continued. * * @since __DEPLOY_VERSION__ */ From ad60577aa4db4c964fae014198c38729787d81ac Mon Sep 17 00:00:00 2001 From: Fedik Date: Thu, 20 Jul 2023 18:09:56 +0300 Subject: [PATCH 3/6] Error Resistible Event --- Tests/DispatcherTest.php | 2 +- src/Dispatcher.php | 32 ++++++++++++++++++++------------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Tests/DispatcherTest.php b/Tests/DispatcherTest.php index b83e6c65..f880d2a5 100644 --- a/Tests/DispatcherTest.php +++ b/Tests/DispatcherTest.php @@ -741,6 +741,6 @@ public function testErrorResistibleEvent() $this->instance->dispatch('onErroredEventTest', $event); - $this->assertEquals(3, count($event->getErrors()), 'The event should collect correct amount of errors.'); + $this->assertEquals(2, count($event->getErrors()), 'The event should collect correct amount of errors.'); } } diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 07b2ee96..86426df7 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -474,10 +474,13 @@ 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()])) { - $errorResistible = $event instanceof ErrorResistibleEventInterface; + return $event; + } + if ($event instanceof ErrorResistibleEventInterface) + { foreach ($this->listeners[$event->getName()] as $listener) { if ($event->isStopped()) @@ -485,23 +488,28 @@ public function dispatch(string $name, ?EventInterface $event = null): EventInte return $event; } - if (!$errorResistible) + try { $listener($event); } - else + catch (\Throwable $e) { - try - { - $listener($event); - } - catch (\Throwable $e) - { - $event->addError($e); - } + $event->addError($e); } } } + else + { + foreach ($this->listeners[$event->getName()] as $listener) + { + if ($event->isStopped()) + { + return $event; + } + + $listener($event); + } + } return $event; } From dd28609d960b1ac98a1a327c980aa47ca01b08cd Mon Sep 17 00:00:00 2001 From: Fedik Date: Thu, 20 Jul 2023 18:17:30 +0300 Subject: [PATCH 4/6] cs --- src/Dispatcher.php | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 50181ead..1237a580 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -445,36 +445,27 @@ 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) - { + if ($event instanceof ErrorResistibleEventInterface) { foreach ($this->listeners[$event->getName()] as $listener) { - if ($event->isStopped()) - { + if ($event->isStopped()) { return $event; } - try - { + try { $listener($event); - } - catch (\Throwable $e) - { + } catch (\Throwable $e) { $event->addError($e); } } - } - else - { + } else { foreach ($this->listeners[$event->getName()] as $listener) { - if ($event->isStopped()) - { + if ($event->isStopped()) { return $event; } From 685c3c4df57ef3c3d849e33569d70e2c4eb834b2 Mon Sep 17 00:00:00 2001 From: Fedik Date: Thu, 20 Jul 2023 18:33:19 +0300 Subject: [PATCH 5/6] cs --- src/Dispatcher.php | 6 ++--- src/ErrorResistibleEventInterface.php | 37 ++++++++++++++------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 1237a580..e5ff7aa5 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -450,8 +450,7 @@ public function dispatch(string $name, ?EventInterface $event = null): EventInte } if ($event instanceof ErrorResistibleEventInterface) { - foreach ($this->listeners[$event->getName()] as $listener) - { + foreach ($this->listeners[$event->getName()] as $listener) { if ($event->isStopped()) { return $event; } @@ -463,8 +462,7 @@ public function dispatch(string $name, ?EventInterface $event = null): EventInte } } } else { - foreach ($this->listeners[$event->getName()] as $listener) - { + foreach ($this->listeners[$event->getName()] as $listener) { if ($event->isStopped()) { return $event; } diff --git a/src/ErrorResistibleEventInterface.php b/src/ErrorResistibleEventInterface.php index 0ac32750..e2a053c5 100644 --- a/src/ErrorResistibleEventInterface.php +++ b/src/ErrorResistibleEventInterface.php @@ -1,4 +1,5 @@ Date: Sat, 29 Jul 2023 20:31:03 +0300 Subject: [PATCH 6/6] Make sure all errors is handled --- Tests/DispatcherTest.php | 26 ++++++++++++-- Tests/Stubs/ErrorResistibleEvent.php | 46 +++++++++--------------- src/Dispatcher.php | 2 +- src/ErrorResistibleEventInterface.php | 17 ++++----- src/ErrorResistibleTrait.php | 52 +++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 43 deletions(-) create mode 100644 src/ErrorResistibleTrait.php diff --git a/Tests/DispatcherTest.php b/Tests/DispatcherTest.php index f880d2a5..96ed63c8 100644 --- a/Tests/DispatcherTest.php +++ b/Tests/DispatcherTest.php @@ -737,10 +737,32 @@ public function testErrorResistibleEvent() throw new \Exception('Event error 2'); }); - $event = new ErrorResistibleEvent('onErroredEventTest'); + $errors = []; + $event = new ErrorResistibleEvent('onErroredEventTest', [], function (\Throwable $e) use (&$errors) { + $errors[] = $e->getMessage(); + }); $this->instance->dispatch('onErroredEventTest', $event); - $this->assertEquals(2, count($event->getErrors()), 'The event should collect correct amount of errors.'); + $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 index 38bdf3f9..334ff54b 100644 --- a/Tests/Stubs/ErrorResistibleEvent.php +++ b/Tests/Stubs/ErrorResistibleEvent.php @@ -7,6 +7,7 @@ namespace Joomla\Event\Tests\Stubs; use Joomla\Event\ErrorResistibleEventInterface; +use Joomla\Event\ErrorResistibleTrait; use Joomla\Event\Event; /** @@ -16,36 +17,21 @@ */ class ErrorResistibleEvent extends Event implements ErrorResistibleEventInterface { - /** - * @var array - * - * @since __DEPLOY_VERSION__ - */ - protected $errors = []; + use ErrorResistibleTrait; - /** - * Add an error that happened during dispatching of the event. - * - * @param \Throwable $error The error instance - * - * @return void - * - * @since __DEPLOY_VERSION__ - */ - public function addError(\Throwable $error): void - { - $this->errors[] = $error; - } + /** + * Constructor. + * + * @param string $name The event name. + * @param array $arguments The event arguments. + * @param ?callable $errorHandler The event arguments. + * + * @since @since __DEPLOY_VERSION__ + */ + public function __construct($name, array $arguments = [], callable $errorHandler = null) + { + parent::__construct($name, $arguments); - /** - * Get list of errors that happened during dispatching of the event. - * - * @return array - * - * @since __DEPLOY_VERSION__ - */ - public function getErrors(): array - { - return $this->errors; - } + $this->errorHandler = $errorHandler; + } } diff --git a/src/Dispatcher.php b/src/Dispatcher.php index e5ff7aa5..b67d7123 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -458,7 +458,7 @@ public function dispatch(string $name, ?EventInterface $event = null): EventInte try { $listener($event); } catch (\Throwable $e) { - $event->addError($e); + $event->handleError($e); } } } else { diff --git a/src/ErrorResistibleEventInterface.php b/src/ErrorResistibleEventInterface.php index e2a053c5..f3cbf688 100644 --- a/src/ErrorResistibleEventInterface.php +++ b/src/ErrorResistibleEventInterface.php @@ -11,30 +11,27 @@ /** * Interface for error resistible events. - * Event do not crash the dispatching process when an exception happening, - * the exception is collected, and the dispatching process is continued. + * Event implementing this interface allows to handle errors of the event listener. * * @since __DEPLOY_VERSION__ */ interface ErrorResistibleEventInterface { /** - * Add an error that happened during dispatching of the event. + * Retrieve error handler for the event. * - * @param \Throwable $error The error instance - * - * @return void + * @return callable * * @since __DEPLOY_VERSION__ */ - public function addError(\Throwable $error): void; + public function getErrorHandler(): callable; /** - * Get list of errors that happened during dispatching of the event. + * Handle the error. * - * @return array + * @param \Throwable $error The error instance * * @since __DEPLOY_VERSION__ */ - public function getErrors(): array; + public function handleError(\Throwable $error): void; } diff --git a/src/ErrorResistibleTrait.php b/src/ErrorResistibleTrait.php new file mode 100644 index 00000000..dc8dfb06 --- /dev/null +++ b/src/ErrorResistibleTrait.php @@ -0,0 +1,52 @@ +errorHandler; + } + + /** + * Handle the error. + * + * @param \Throwable $error The error instance + * + * @since __DEPLOY_VERSION__ + */ + public function handleError(\Throwable $error): void + { + $handler = $this->getErrorHandler(); + $handler($error); + } +}