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

[4.x] Make error renderers in the error handler customizable #2734

Merged
merged 2 commits into from Jun 25, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
71 changes: 42 additions & 29 deletions Slim/Handlers/ErrorHandler.php
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
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