Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Finalhandler production stacktrace fix #46

Closed
wants to merge 3 commits into from
Closed
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: 19 additions & 9 deletions src/FinalHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,9 @@ class FinalHandler
*/
public function __construct(array $options = [], ResponseInterface $response = null)
{
$this->options = $options;
$this->response = $response;
$this->options = $options;

if ($response) {
$this->bodySize = $response->getBody()->getSize();
}
$this->setOriginalResponse($response);
}

/**
Expand Down Expand Up @@ -97,6 +94,20 @@ public function __invoke(RequestInterface $request, ResponseInterface $response,
return $this->create404($request, $response);
}

/**
* Set the original response and response body size for comparison.
*
* @param ResponseInterface $response
*/
public function setOriginalResponse(ResponseInterface $response = null)
{
$this->response = $response;

if ($response) {
$this->bodySize = $response->getBody()->getSize();
}
}

/**
* Handle an error condition
*
Expand All @@ -110,13 +121,12 @@ public function __invoke(RequestInterface $request, ResponseInterface $response,
private function handleError($error, RequestInterface $request, ResponseInterface $response)
{
$response = $response->withStatus(
Utils::getStatusCode($error, $response)
Utils::getStatusCode($error, $response),
$response->getReasonPhrase()
);

$message = $response->getReasonPhrase() ?: 'Unknown Error';
if (! isset($this->options['env'])
|| $this->options['env'] !== 'production'
) {
if (isset($this->options['env']) && $this->options['env'] !== 'production') {
$message = $this->createDevelopmentErrorMessage($error);
}

Expand Down
48 changes: 48 additions & 0 deletions test/FinalHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ public function testInvokingWithExceptionWithInvalidCodeSetsStatusTo500()
public function testInvokingWithErrorInNonProductionModeSetsResponseBodyToError()
{
$error = 'error';
$this->final = new FinalHandler(['env' => 'not-production']);
$response = call_user_func($this->final, $this->request, $this->response, $error);
$this->assertEquals($error, (string) $response->getBody());
}

public function testInvokingWithExceptionInNonProductionModeIncludesExceptionMessageInResponseBody()
{
$error = new Exception('foo', 400);
$this->final = new FinalHandler(['env' => 'not-production']);
$response = call_user_func($this->final, $this->request, $this->response, $error);
$expected = $this->escaper->escapeHtml($error->getMessage());
$this->assertContains($expected, (string) $response->getBody());
Expand All @@ -69,11 +71,35 @@ public function testInvokingWithExceptionInNonProductionModeIncludesExceptionMes
public function testInvokingWithExceptionInNonProductionModeIncludesTraceInResponseBody()
{
$error = new Exception('foo', 400);
$this->final = new FinalHandler(['env' => 'not-production']);
$response = call_user_func($this->final, $this->request, $this->response, $error);
$expected = $this->escaper->escapeHtml($error->getTraceAsString());
$this->assertContains($expected, (string) $response->getBody());
}

public function testInvokingWithErrorAndNoEnvironmentModeSetDoesNotSetResponseBodyToError()
{
$error = 'error';
$response = call_user_func($this->final, $this->request, $this->response, $error);
$this->assertNotEquals($error, (string) $response->getBody());
}

public function testInvokingWithExceptionAndNoEnvironmentModeSetDoesNotIncludeExceptionMessageInResponseBody()
{
$error = new Exception('foo', 400);
$response = call_user_func($this->final, $this->request, $this->response, $error);
$expected = $this->escaper->escapeHtml($error->getMessage());
$this->assertNotContains($expected, (string) $response->getBody());
}

public function testInvokingWithExceptionAndNoEnvironmentModeSetDoesNotIncludeTraceInResponseBody()
{
$error = new Exception('foo', 400);
$response = call_user_func($this->final, $this->request, $this->response, $error);
$expected = $this->escaper->escapeHtml($error->getTraceAsString());
$this->assertNotContains($expected, (string) $response->getBody());
}

public function testInvokingWithErrorInProductionSetsResponseToReasonPhrase()
{
$final = new FinalHandler([
Expand Down Expand Up @@ -110,6 +136,13 @@ public function testCreates404ResponseWhenNoErrorIsPresent()
$this->assertEquals(404, $response->getStatusCode());
}

public function testErrorResponsePreservesOriginalReasonPhraseIfSet()
{
$this->response = $this->response->withStatus(500, 'It broke!');
$response = call_user_func($this->final, $this->request, $this->response, new \Exception('foo'));
$this->assertSame($this->response->getReasonPhrase(), $response->getReasonPhrase());
}

public function test404ResponseIncludesOriginalRequestUri()
{
$originalUrl = 'http://local.example.com/bar/foo';
Expand Down Expand Up @@ -150,4 +183,19 @@ public function testReturnsResponseIfBodyLengthHasChanged()
$result = $final(new Request(new PsrRequest()), $response);
$this->assertSame($response, $result);
}

public function testCanReplaceOriginalResponseAndBodySizeAfterConstruction()
{
$psrResponse = new PsrResponse();
$originalResponse = new Response(new PsrResponse());
$originalResponse->write('foo');

$final = new FinalHandler([], $psrResponse);
$final->setOriginalResponse($originalResponse);

/** @var Response $actualResponse */
$actualResponse = self::readAttribute($final, 'response');
$this->assertSame($originalResponse, $actualResponse);
$this->assertSame(3, $actualResponse->getBody()->getSize());
}
}