Skip to content
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
7 changes: 5 additions & 2 deletions Slim/Handlers/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class ErrorHandler implements ErrorHandlerInterface

protected ?string $contentType = null;

protected bool $isContentTypeForced = false;

protected ?string $method = null;

protected ServerRequestInterface $request;
Expand Down Expand Up @@ -125,7 +127,7 @@ public function __invoke(
$this->exception = $exception;
$this->method = $request->getMethod();
$this->statusCode = $this->determineStatusCode();
if ($this->contentType === null) {
if (!$this->isContentTypeForced) {
$this->contentType = $this->determineContentType($request);
}

Expand All @@ -139,10 +141,11 @@ public function __invoke(
/**
* Force the content type for all error handler responses.
*
* @param string|null $contentType The content type
* @param string|null $contentType The content type. Null restores Accept-header negotiation.
*/
public function forceContentType(?string $contentType): void
{
$this->isContentTypeForced = $contentType !== null;
$this->contentType = $contentType;
}

Expand Down
95 changes: 95 additions & 0 deletions tests/Handlers/ErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,101 @@ public function testForceContentType()
$this->assertSame(['application/json'], $response->getHeader('Content-Type'));
}

/**
* Test that the content type is negotiated from the Accept header on each
* request instead of being cached from the first invocation.
*/
public function testContentTypeIsNegotiatedOnEachRequest()
{
$handler = new ErrorHandler($this->getCallableResolver(), $this->getResponseFactory());

$jsonRequest = $this
->createServerRequest('/not-defined', 'GET')
->withHeader('Accept', 'application/json');

$htmlRequest = $this
->createServerRequest('/not-defined', 'GET')
->withHeader('Accept', 'text/html');

$exception = new HttpNotFoundException($jsonRequest);

/** @var ResponseInterface $response */
$response = $handler->__invoke($jsonRequest, $exception, false, false, false);
$this->assertSame(['application/json'], $response->getHeader('Content-Type'));

$exception = new HttpNotFoundException($htmlRequest);

/** @var ResponseInterface $response */
$response = $handler->__invoke($htmlRequest, $exception, false, false, false);
$this->assertSame(['text/html'], $response->getHeader('Content-Type'));
}

/**
* Test that a forced content type still wins over per-request negotiation.
*/
public function testForcedContentTypeWinsOverSubsequentNegotiation()
{
$handler = new ErrorHandler($this->getCallableResolver(), $this->getResponseFactory());
$handler->forceContentType('application/json');

$jsonRequest = $this
->createServerRequest('/not-defined', 'GET')
->withHeader('Accept', 'application/json');

$xmlRequest = $this
->createServerRequest('/not-defined', 'GET')
->withHeader('Accept', 'application/xml');

$exception = new HttpNotFoundException($jsonRequest);

/** @var ResponseInterface $response */
$response = $handler->__invoke($jsonRequest, $exception, false, false, false);
$this->assertSame(['application/json'], $response->getHeader('Content-Type'));

$exception = new HttpNotFoundException($xmlRequest);

/** @var ResponseInterface $response */
$response = $handler->__invoke($xmlRequest, $exception, false, false, false);
$this->assertSame(['application/json'], $response->getHeader('Content-Type'));
}

/**
* Test that forceContentType(null) restores Accept-header negotiation.
*/
public function testForceContentTypeNullRestoresNegotiation()
{
$handler = new ErrorHandler($this->getCallableResolver(), $this->getResponseFactory());
$handler->forceContentType('application/json');

$xmlRequest = $this
->createServerRequest('/not-defined', 'GET')
->withHeader('Accept', 'application/xml');

$htmlRequest = $this
->createServerRequest('/not-defined', 'GET')
->withHeader('Accept', 'text/html');

$exception = new HttpNotFoundException($xmlRequest);

/** @var ResponseInterface $response */
$response = $handler->__invoke($xmlRequest, $exception, false, false, false);
$this->assertSame(['application/json'], $response->getHeader('Content-Type'));

$handler->forceContentType(null);

$exception = new HttpNotFoundException($xmlRequest);

/** @var ResponseInterface $response */
$response = $handler->__invoke($xmlRequest, $exception, false, false, false);
$this->assertSame(['application/xml'], $response->getHeader('Content-Type'));

$exception = new HttpNotFoundException($htmlRequest);

/** @var ResponseInterface $response */
$response = $handler->__invoke($htmlRequest, $exception, false, false, false);
$this->assertSame(['text/html'], $response->getHeader('Content-Type'));
}

public function testHalfValidContentType()
{
$request = $this
Expand Down
Loading