Skip to content

Commit

Permalink
Merge pull request #2862 from nickdnk/4.x
Browse files Browse the repository at this point in the history
Optionally handle subclasses of exceptions in custom error handler
  • Loading branch information
l0gicgate authored Dec 10, 2019
2 parents ea75cf4 + 7ea0a3d commit 941828c
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 7 deletions.
28 changes: 25 additions & 3 deletions Slim/Middleware/ErrorMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
}
113 changes: 109 additions & 4 deletions tests/Middleware/ErrorMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 941828c

Please # to comment.