diff --git a/src/ExceptionHandler.php b/src/ExceptionHandler.php index 4b6f3fd..384da4b 100644 --- a/src/ExceptionHandler.php +++ b/src/ExceptionHandler.php @@ -26,6 +26,11 @@ class ExceptionHandler */ protected $messageTemplate; + /** + * @var callable[] + */ + private $listeners = []; + /** * @param Console $console */ @@ -81,6 +86,8 @@ public function __invoke($exception) $this->console->write($message); $this->console->writeLine(''); + $this->triggerListeners($exception); + // Exceptions always indicate an error status; however, most have a // code of zero; set it to 1 in such cases. $exitCode = $exception->getCode(); @@ -166,4 +173,39 @@ private function validateException($exception) )); } } + + /** + * Attach an error listener. + * + * Each listener receives one argument: + * + * - Throwable|Exception $error + * + * These instances are all immutable, and the return values of + * listeners are ignored; use listeners for reporting purposes + * only. + * + * @param callable $listener + * @return void + */ + public function attachListener(callable $listener) + { + if (in_array($listener, $this->listeners, true)) { + return; + } + + $this->listeners[] = $listener; + } + + /** + * Trigger all error listeners. + * @param Exception|Throwable $exception + * @return void + */ + private function triggerListeners($exception) + { + foreach ($this->listeners as $listener) { + $listener($exception); + } + } } diff --git a/test/ExceptionHandlerTest.php b/test/ExceptionHandlerTest.php index 8f18192..4436aff 100644 --- a/test/ExceptionHandlerTest.php +++ b/test/ExceptionHandlerTest.php @@ -71,4 +71,34 @@ public function testCreateMessageFillsExpectedVariablesForExceptionWithPrevious( } $this->assertNotContains('Previous: :previous', $message); } + + public function testErrorHandlingTriggersListeners() + { + $exception = new RuntimeException('Exception raised', 1); + + $handlerCount = 0; + + $listener = function ($error) use ($exception, &$handlerCount) { + $this->assertSame($exception, $error, 'Listener did not receive same exception as was raised'); + ++$handlerCount; + }; + + $listener2 = clone $listener; + + $this->handler->attachListener($listener); + $this->handler->attachListener($listener2); + + $exitCode = $this->handler->__invoke($exception); + + $this->assertSame(2, $handlerCount); + } + + public function testTheSameListenerIsAttachedOnlyOnce() + { + $listener = function () { + }; + $this->handler->attachListener($listener); + $this->handler->attachListener($listener); + $this->assertAttributeCount(1, 'listeners', $this->handler); + } }