Skip to content

Commit

Permalink
Merge pull request #2734 from adriansuter/path-customErrorRenderer
Browse files Browse the repository at this point in the history
[4.x] Make error renderers in the error handler customizable
  • Loading branch information
l0gicgate authored Jun 25, 2019
2 parents c120b49 + c821bba commit 6d6d34e
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 44 deletions.
71 changes: 42 additions & 29 deletions Slim/Handlers/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,19 @@
class ErrorHandler implements ErrorHandlerInterface
{
/**
* Known handled content types
*
* @var array
* @var string
*/
protected $defaultErrorRenderer = HtmlErrorRenderer::class;

/**
* @var string[]
*/
protected $knownContentTypes = [
'application/json',
'application/xml',
'text/xml',
'text/html',
'text/plain'
protected $errorRenderers = [
'application/json' => JsonErrorRenderer::class,
'application/xml' => XmlErrorRenderer::class,
'text/xml' => XmlErrorRenderer::class,
'text/html' => HtmlErrorRenderer::class,
'text/plain' => PlainTextErrorRenderer::class,
];

/**
Expand Down Expand Up @@ -166,7 +169,10 @@ protected function determineStatusCode(): int
protected function determineContentType(ServerRequestInterface $request): string
{
$acceptHeader = $request->getHeaderLine('Accept');
$selectedContentTypes = array_intersect(explode(',', $acceptHeader), $this->knownContentTypes);
$selectedContentTypes = array_intersect(
explode(',', $acceptHeader),
array_keys($this->errorRenderers)
);
$count = count($selectedContentTypes);

if ($count) {
Expand All @@ -185,7 +191,7 @@ protected function determineContentType(ServerRequestInterface $request): string

if (preg_match('/\+(json|xml)/', $acceptHeader, $matches)) {
$mediaType = 'application/' . $matches[1];
if (in_array($mediaType, $this->knownContentTypes, true)) {
if (array_key_exists($mediaType, $this->errorRenderers)) {
return $mediaType;
}
}
Expand Down Expand Up @@ -218,30 +224,37 @@ protected function determineRenderer(): ErrorRendererInterface
}

if ($renderer === null) {
switch ($this->contentType) {
case 'application/json':
$renderer = JsonErrorRenderer::class;
break;

case 'text/xml':
case 'application/xml':
$renderer = XmlErrorRenderer::class;
break;

case 'text/plain':
$renderer = PlainTextErrorRenderer::class;
break;

default:
case 'text/html':
$renderer = HtmlErrorRenderer::class;
break;
if (array_key_exists($this->contentType, $this->errorRenderers)) {
$renderer = $this->errorRenderers[$this->contentType];
} else {
$renderer = $this->defaultErrorRenderer;
}
}

return new $renderer();
}

/**
* Register an error renderer for a specific content-type
*
* @param string $contentType The content-type this renderer should be registered to
* @param string $errorRenderer The error renderer class name
*/
public function registerErrorRenderer(string $contentType, string $errorRenderer): void
{
$this->errorRenderers[$contentType] = $errorRenderer;
}

/**
* Set the default error renderer
*
* @param string $errorRenderer The default error renderer class name
*/
public function setDefaultErrorRenderer(string $errorRenderer): void
{
$this->defaultErrorRenderer = $errorRenderer;
}

/**
* Write to the error log if $logErrors has been set to true
*
Expand Down
62 changes: 47 additions & 15 deletions tests/Handlers/ErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use Psr\Http\Message\ResponseInterface;
use ReflectionClass;
use Slim\Error\Renderers\HtmlErrorRenderer;
use Slim\Error\Renderers\JsonErrorRenderer;
use Slim\Error\Renderers\PlainTextErrorRenderer;
use Slim\Error\Renderers\XmlErrorRenderer;
Expand Down Expand Up @@ -87,6 +88,11 @@ public function testDetermineRenderer()
$reflectionProperty->setValue($handler, 'text/plain');
$renderer = $method->invoke($handler);
$this->assertInstanceOf(PlainTextErrorRenderer::class, $renderer);

// Test the default error renderer
$reflectionProperty->setValue($handler, 'text/unknown');
$renderer = $method->invoke($handler);
$this->assertInstanceOf(HtmlErrorRenderer::class, $renderer);
}

public function testDetermineStatusCode()
Expand Down Expand Up @@ -128,10 +134,10 @@ public function testHalfValidContentType()
->getMockBuilder(ErrorHandler::class)
->disableOriginalConstructor()
->getMock();
$newTypes = [
'application/xml',
'text/xml',
'text/html',
$newErrorRenderers = [
'application/xml' => XmlErrorRenderer::class,
'text/xml' => XmlErrorRenderer::class,
'text/html' => HtmlErrorRenderer::class,
];

$class = new ReflectionClass(ErrorHandler::class);
Expand All @@ -140,9 +146,9 @@ public function testHalfValidContentType()
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($handler, $this->getResponseFactory());

$reflectionProperty = $class->getProperty('knownContentTypes');
$reflectionProperty = $class->getProperty('errorRenderers');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($handler, $newTypes);
$reflectionProperty->setValue($handler, $newErrorRenderers);

$method = $class->getMethod('determineContentType');
$method->setAccessible(true);
Expand All @@ -164,9 +170,9 @@ public function testDetermineContentTypeTextPlainMultiAcceptHeader()
->disableOriginalConstructor()
->getMock();

$knownContentTypes = [
'text/plain',
'text/xml',
$errorRenderers = [
'text/plain' => PlainTextErrorRenderer::class,
'text/xml' => XmlErrorRenderer::class,
];

$class = new ReflectionClass(ErrorHandler::class);
Expand All @@ -175,9 +181,9 @@ public function testDetermineContentTypeTextPlainMultiAcceptHeader()
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($handler, $this->getResponseFactory());

$reflectionProperty = $class->getProperty('knownContentTypes');
$reflectionProperty = $class->getProperty('errorRenderers');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($handler, $knownContentTypes);
$reflectionProperty->setValue($handler, $errorRenderers);

$method = $class->getMethod('determineContentType');
$method->setAccessible(true);
Expand All @@ -199,8 +205,8 @@ public function testDetermineContentTypeApplicationJsonOrXml()
->disableOriginalConstructor()
->getMock();

$knownContentTypes = [
'application/xml'
$errorRenderers = [
'application/xml' => XmlErrorRenderer::class
];

$class = new ReflectionClass(ErrorHandler::class);
Expand All @@ -209,9 +215,9 @@ public function testDetermineContentTypeApplicationJsonOrXml()
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($handler, $this->getResponseFactory());

$reflectionProperty = $class->getProperty('knownContentTypes');
$reflectionProperty = $class->getProperty('errorRenderers');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($handler, $knownContentTypes);
$reflectionProperty->setValue($handler, $errorRenderers);

$method = $class->getMethod('determineContentType');
$method->setAccessible(true);
Expand Down Expand Up @@ -252,6 +258,32 @@ public function testAcceptableMediaTypeIsNotFirstInList()
$this->assertEquals('text/html', $return);
}

public function testRegisterErrorRenderer()
{
$handler = new ErrorHandler($this->getResponseFactory());
$handler->registerErrorRenderer('application/slim', PlainTextErrorRenderer::class);

$reflectionClass = new ReflectionClass(ErrorHandler::class);
$reflectionProperty = $reflectionClass->getProperty('errorRenderers');
$reflectionProperty->setAccessible(true);
$errorRenderers = $reflectionProperty->getValue($handler);

$this->assertArrayHasKey('application/slim', $errorRenderers);
}

public function testSetDefaultErrorRenderer()
{
$handler = new ErrorHandler($this->getResponseFactory());
$handler->setDefaultErrorRenderer(PlainTextErrorRenderer::class);

$reflectionClass = new ReflectionClass(ErrorHandler::class);
$reflectionProperty = $reflectionClass->getProperty('defaultErrorRenderer');
$reflectionProperty->setAccessible(true);
$defaultErrorRenderer = $reflectionProperty->getValue($handler);

$this->assertEquals(PlainTextErrorRenderer::class, $defaultErrorRenderer);
}

public function testOptions()
{
$request = $this->createServerRequest('/', 'OPTIONS');
Expand Down

0 comments on commit 6d6d34e

Please sign in to comment.