Skip to content

Commit 1bbd7f9

Browse files
committed
Update Response class to build on top of abstract message class
1 parent 33a0cf3 commit 1bbd7f9

File tree

3 files changed

+128
-11
lines changed

3 files changed

+128
-11
lines changed

src/Message/Response.php

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
namespace React\Http\Message;
44

55
use Fig\Http\Message\StatusCodeInterface;
6+
use Psr\Http\Message\ResponseInterface;
67
use Psr\Http\Message\StreamInterface;
78
use React\Http\Io\BufferedBody;
89
use React\Http\Io\HttpBodyStream;
910
use React\Stream\ReadableStreamInterface;
10-
use RingCentral\Psr7\Response as Psr7Response;
11+
use RingCentral\Psr7\MessageTrait;
1112

1213
/**
1314
* Represents an outgoing server response message.
@@ -40,7 +41,7 @@
4041
*
4142
* @see \Psr\Http\Message\ResponseInterface
4243
*/
43-
final class Response extends Psr7Response implements StatusCodeInterface
44+
final class Response extends MessageTrait implements ResponseInterface, StatusCodeInterface
4445
{
4546
/**
4647
* Create an HTML response
@@ -257,6 +258,41 @@ public static function xml($xml)
257258
return new self(self::STATUS_OK, array('Content-Type' => 'application/xml'), $xml);
258259
}
259260

261+
/**
262+
* @var bool
263+
* @see self::$phrasesMap
264+
*/
265+
private static $phrasesInitialized = false;
266+
267+
/**
268+
* Map of standard HTTP status codes to standard reason phrases.
269+
*
270+
* This map will be fully populated with all standard reason phrases on
271+
* first access. By default, it only contains a subset of HTTP status codes
272+
* that have a custom mapping to reason phrases (such as those with dashes
273+
* and all caps words). See `self::STATUS_*` for all possible status code
274+
* constants.
275+
*
276+
* @var array<int,string>
277+
* @see self::STATUS_*
278+
* @see self::getReasonPhraseForStatusCode()
279+
*/
280+
private static $phrasesMap = array(
281+
200 => 'OK',
282+
203 => 'Non-Authoritative Information',
283+
207 => 'Multi-Status',
284+
226 => 'IM Used',
285+
414 => 'URI Too Large',
286+
418 => 'I\'m a teapot',
287+
505 => 'HTTP Version Not Supported'
288+
);
289+
290+
/** @var int */
291+
private $statusCode;
292+
293+
/** @var string */
294+
private $reasonPhrase;
295+
260296
/**
261297
* @param int $status HTTP status code (e.g. 200/404), see `self::STATUS_*` constants
262298
* @param array<string,string|string[]> $headers additional response headers
@@ -280,12 +316,60 @@ public function __construct(
280316
throw new \InvalidArgumentException('Invalid response body given');
281317
}
282318

283-
parent::__construct(
284-
$status,
285-
$headers,
286-
$body,
287-
$version,
288-
$reason
289-
);
319+
$this->protocol = (string) $version;
320+
$this->setHeaders($headers);
321+
$this->stream = $body;
322+
323+
$this->statusCode = (int) $status;
324+
$this->reasonPhrase = ($reason !== '' && $reason !== null) ? (string) $reason : self::getReasonPhraseForStatusCode($status);
325+
}
326+
327+
public function getStatusCode()
328+
{
329+
return $this->statusCode;
330+
}
331+
332+
public function withStatus($code, $reasonPhrase = '')
333+
{
334+
if ((string) $reasonPhrase === '') {
335+
$reasonPhrase = self::getReasonPhraseForStatusCode($code);
336+
}
337+
338+
if ($this->statusCode === (int) $code && $this->reasonPhrase === (string) $reasonPhrase) {
339+
return $this;
340+
}
341+
342+
$response = clone $this;
343+
$response->statusCode = (int) $code;
344+
$response->reasonPhrase = (string) $reasonPhrase;
345+
346+
return $response;
347+
}
348+
349+
public function getReasonPhrase()
350+
{
351+
return $this->reasonPhrase;
352+
}
353+
354+
/**
355+
* @param int $code
356+
* @return string default reason phrase for given status code or empty string if unknown
357+
*/
358+
private static function getReasonPhraseForStatusCode($code)
359+
{
360+
if (!self::$phrasesInitialized) {
361+
self::$phrasesInitialized = true;
362+
363+
// map all `self::STATUS_` constants from status code to reason phrase
364+
// e.g. `self::STATUS_NOT_FOUND = 404` will be mapped to `404 Not Found`
365+
$ref = new \ReflectionClass(__CLASS__);
366+
foreach ($ref->getConstants() as $name => $value) {
367+
if (!isset(self::$phrasesMap[$value]) && \strpos($name, 'STATUS_') === 0) {
368+
self::$phrasesMap[$value] = \ucwords(\strtolower(\str_replace('_', ' ', \substr($name, 7))));
369+
}
370+
}
371+
}
372+
373+
return isset(self::$phrasesMap[$code]) ? self::$phrasesMap[$code] : '';
290374
}
291375
}

tests/Io/StreamingServerTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,9 +1567,9 @@ function ($data) use (&$buffer) {
15671567

15681568
$this->assertInstanceOf('InvalidArgumentException', $error);
15691569

1570-
$this->assertContainsString("HTTP/1.1 505 HTTP Version not supported\r\n", $buffer);
1570+
$this->assertContainsString("HTTP/1.1 505 HTTP Version Not Supported\r\n", $buffer);
15711571
$this->assertContainsString("\r\n\r\n", $buffer);
1572-
$this->assertContainsString("Error 505: HTTP Version not supported", $buffer);
1572+
$this->assertContainsString("Error 505: HTTP Version Not Supported", $buffer);
15731573
}
15741574

15751575
public function testRequestOverflowWillEmitErrorAndSendErrorResponse()

tests/Message/ResponseTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,39 @@ public function testResourceBodyWillThrow()
5454
new Response(200, array(), tmpfile());
5555
}
5656

57+
public function testWithStatusReturnsNewInstanceWhenStatusIsChanged()
58+
{
59+
$response = new Response(200);
60+
61+
$new = $response->withStatus(404);
62+
$this->assertNotSame($response, $new);
63+
$this->assertEquals(404, $new->getStatusCode());
64+
$this->assertEquals('Not Found', $new->getReasonPhrase());
65+
$this->assertEquals(200, $response->getStatusCode());
66+
$this->assertEquals('OK', $response->getReasonPhrase());
67+
}
68+
69+
public function testWithStatusReturnsSameInstanceWhenStatusIsUnchanged()
70+
{
71+
$response = new Response(200);
72+
73+
$new = $response->withStatus(200);
74+
$this->assertSame($response, $new);
75+
$this->assertEquals(200, $response->getStatusCode());
76+
$this->assertEquals('OK', $response->getReasonPhrase());
77+
}
78+
79+
public function testWithStatusReturnsNewInstanceWhenStatusIsUnchangedButReasonIsChanged()
80+
{
81+
$response = new Response(200);
82+
83+
$new = $response->withStatus(200, 'Quite Ok');
84+
$this->assertNotSame($response, $new);
85+
$this->assertEquals(200, $new->getStatusCode());
86+
$this->assertEquals('Quite Ok', $new->getReasonPhrase());
87+
$this->assertEquals(200, $response->getStatusCode());
88+
$this->assertEquals('OK', $response->getReasonPhrase());
89+
}
5790

5891
public function testHtmlMethodReturnsHtmlResponse()
5992
{

0 commit comments

Comments
 (0)