Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally handle subclasses of exceptions in custom error handler #2862

Merged
merged 2 commits into from
Dec 10, 2019
Merged
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
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