diff --git a/Slim/Middleware/ErrorMiddleware.php b/Slim/Middleware/ErrorMiddleware.php index 194c57ce4..47232d347 100644 --- a/Slim/Middleware/ErrorMiddleware.php +++ b/Slim/Middleware/ErrorMiddleware.php @@ -48,10 +48,15 @@ class ErrorMiddleware implements MiddlewareInterface protected $logErrorDetails; /** - * @var array + * @var ErrorHandlerInterface[]|callable[] */ protected $handlers = []; + /** + * @var ErrorHandlerInterface[]|callable[] + */ + protected $subClassHandlers = []; + /** * @var ErrorHandlerInterface|callable|null */ @@ -120,6 +125,14 @@ public function getErrorHandler(string $type) { if (isset($this->handlers[$type])) { return $this->callableResolver->resolve($this->handlers[$type]); + } elseif (isset($this->subClassHandlers[$type])) { + return $this->callableResolver->resolve($this->subClassHandlers[$type]); + } else { + foreach ($this->subClassHandlers as $class => $handler) { + if (is_subclass_of($type, $class)) { + return $this->callableResolver->resolve($handler); + } + } } return $this->getDefaultErrorHandler(); @@ -170,6 +183,9 @@ public function setDefaultErrorHandler($handler): self * * The callable signature MUST match the ErrorHandlerInterface * + * Pass true to $handleSubclasses to make the handler handle all subclasses of + * the type as well. + * * @see \Slim\Interfaces\ErrorHandlerInterface * * 1. Instance of \Psr\Http\Message\ServerRequestInterface @@ -183,11 +199,17 @@ public function setDefaultErrorHandler($handler): self * * @param string $type Exception/Throwable name. ie: RuntimeException::class * @param callable|ErrorHandlerInterface $handler + * @param bool $handleSubclasses * @return self */ - public function setErrorHandler(string $type, $handler): self + public function setErrorHandler(string $type, $handler, bool $handleSubclasses = false): self { - $this->handlers[$type] = $handler; + if ($handleSubclasses) { + $this->subClassHandlers[$type] = $handler; + } else { + $this->handlers[$type] = $handler; + } + return $this; } } diff --git a/tests/Middleware/ErrorMiddlewareTest.php b/tests/Middleware/ErrorMiddlewareTest.php index e238d8b2c..d7a543071 100644 --- a/tests/Middleware/ErrorMiddlewareTest.php +++ b/tests/Middleware/ErrorMiddlewareTest.php @@ -10,6 +10,8 @@ namespace Slim\Tests\Middleware; use Error; +use InvalidArgumentException; +use LogicException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Slim\App; @@ -99,24 +101,127 @@ public function testGetErrorHandlerWillReturnDefaultErrorHandlerForUnhandledExce $this->assertInstanceOf(ErrorHandler::class, $handler); } + public function testSuperclassExceptionHandlerHandlesExceptionWithSubclassExactMatch() + { + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $callableResolver = $app->getCallableResolver(); + $mw = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); + $app->add(function ($request, $handler) { + throw new LogicException('This is a LogicException...'); + }); + $mw->setErrorHandler(LogicException::class, (function (ServerRequestInterface $request, $exception) { + $response = $this->createResponse(); + $response->getBody()->write($exception->getMessage()); + return $response; + })->bindTo($this), true); // - true; handle subclass but also LogicException explicitly + $mw->setDefaultErrorHandler((function () { + $response = $this->createResponse(); + $response->getBody()->write('Oops..'); + return $response; + })->bindTo($this)); + $app->add($mw); + $app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('...'); + return $response; + }); + $request = $this->createServerRequest('/foo'); + $app->run($request); + $this->expectOutputString('This is a LogicException...'); + } + + public function testSuperclassExceptionHandlerHandlesSubclassException() + { + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $callableResolver = $app->getCallableResolver(); + + $mw = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); + + $app->add(function ($request, $handler) { + throw new InvalidArgumentException('This is a subclass of LogicException...'); + }); + + $mw->setErrorHandler(LogicException::class, (function (ServerRequestInterface $request, $exception) { + $response = $this->createResponse(); + $response->getBody()->write($exception->getMessage()); + return $response; + })->bindTo($this), true); // - true; handle subclass + + $mw->setDefaultErrorHandler((function () { + $response = $this->createResponse(); + $response->getBody()->write('Oops..'); + return $response; + })->bindTo($this)); + + $app->add($mw); + + $app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('...'); + return $response; + }); + + $request = $this->createServerRequest('/foo'); + $app->run($request); + + $this->expectOutputString('This is a subclass of LogicException...'); + } + + public function testSuperclassExceptionHandlerDoesNotHandleSubclassException() + { + $responseFactory = $this->getResponseFactory(); + $app = new App($responseFactory); + $callableResolver = $app->getCallableResolver(); + + $mw = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); + + $app->add(function ($request, $handler) { + throw new InvalidArgumentException('This is a subclass of LogicException...'); + }); + + $mw->setErrorHandler(LogicException::class, (function (ServerRequestInterface $request, $exception) { + $response = $this->createResponse(); + $response->getBody()->write($exception->getMessage()); + return $response; + })->bindTo($this), false); // - false; don't handle subclass + + $mw->setDefaultErrorHandler((function () { + $response = $this->createResponse(); + $response->getBody()->write('Oops..'); + return $response; + })->bindTo($this)); + + $app->add($mw); + + $app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) { + $response->getBody()->write('...'); + return $response; + }); + + $request = $this->createServerRequest('/foo'); + $app->run($request); + + $this->expectOutputString('Oops..'); + } + public function testErrorHandlerHandlesThrowables() { $responseFactory = $this->getResponseFactory(); $app = new App($responseFactory); $callableResolver = $app->getCallableResolver(); + $mw = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); + $app->add(function ($request, $handler) { throw new Error('Oops..'); }); - $handler = (function (ServerRequestInterface $request, $exception) { + $mw->setDefaultErrorHandler((function (ServerRequestInterface $request, $exception) { $response = $this->createResponse(); $response->getBody()->write($exception->getMessage()); return $response; - })->bindTo($this); + })->bindTo($this)); - $mw = new ErrorMiddleware($callableResolver, $this->getResponseFactory(), false, false, false); - $mw->setDefaultErrorHandler($handler); $app->add($mw); $app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) {