Add Responder

This commit is contained in:
PJ Dietz 2015-04-10 00:15:35 -04:00
parent 15ddaa1dd2
commit df8e274f26
3 changed files with 201 additions and 0 deletions

52
src/Routing/Responder.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace WellRESTed\Routing;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamableInterface;
class Responder
{
public function respond(ResponseInterface $response, $chunkSize = 0)
{
// Status Line
header($this->getStatusLine($response));
// Headers
foreach ($response->getHeaders() as $key => $headers) {
$replace = true;
foreach ($headers as $header) {
header("$key: $header", $replace);
$replace = false;
}
}
// Body
$body = $response->getBody();
if ($body->isReadable()) {
$this->outputBody($response->getBody(), $chunkSize);
}
}
private function getStatusLine(ResponseInterface $response)
{
$protocol = $response->getProtocolVersion();
$statusCode = $response->getStatusCode();
$reasonPhrase = $response->getReasonPhrase();
if ($reasonPhrase) {
return "HTTP/$protocol $statusCode $reasonPhrase";
} else {
return "HTTP/$protocol $statusCode";
}
}
private function outputBody(StreamableInterface $body, $chunkSize)
{
if ($chunkSize > 0) {
$body->rewind();
while (!$body->eof()) {
print $body->read($chunkSize);
}
} else {
print (string) $body;
}
}
}

28
test/src/HeaderStack.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace WellRESTed\Routing;
class HeaderStack
{
private static $headers;
public static function reset()
{
self::$headers = [];
}
public static function push($header)
{
self::$headers[] = $header;
}
public static function getHeaders()
{
return self::$headers;
}
}
function header($string, $dummy = true)
{
HeaderStack::push($string);
}

View File

@ -0,0 +1,121 @@
<?php
namespace WellRESTed\Test\Unit\Routing;
use Prophecy\Argument;
use WellRESTed\Routing\HeaderStack;
use WellRESTed\Routing\Responder;
require_once(__DIR__ . "/../../../src/HeaderStack.php");
/**
* @covers WellRESTed\Routing\Responder
*/
class ResponderTest extends \PHPUnit_Framework_TestCase
{
private $response;
private $body;
public function setUp()
{
HeaderStack::reset();
$this->body = $this->prophesize('\Psr\Http\Message\StreamableInterface');
$this->body->isReadable()->willReturn(false);
$this->response = $this->prophesize('\Psr\Http\Message\ResponseInterface');
$this->response->getHeaders()->willReturn([]);
$this->response->getProtocolVersion()->willReturn("1.1");
$this->response->getStatusCode()->willReturn("200");
$this->response->getReasonPhrase()->willReturn("Ok");
$this->response->getBody()->willReturn($this->body->reveal());
}
public function testSendStatusCodeWithReasonPhrase()
{
$this->response->getStatusCode()->willReturn("200");
$this->response->getReasonPhrase()->willReturn("Ok");
$responder = new Responder();
$responder->respond($this->response->reveal());
$this->assertContains("HTTP/1.1 200 Ok", HeaderStack::getHeaders());
}
public function testSendStatusCodeWithoutReasonPhrase()
{
$this->response->getStatusCode()->willReturn("999");
$this->response->getReasonPhrase()->willReturn(null);
$responder = new Responder();
$responder->respond($this->response->reveal());
$this->assertContains("HTTP/1.1 999", HeaderStack::getHeaders());
}
/**
* @dataProvider headerProvider
*/
public function testSendsHeaders($header)
{
$this->response->getHeaders()->willReturn([
"Content-length" => ["2048"],
"X-foo" => ["bar", "baz"],
]);
$responder = new Responder();
$responder->respond($this->response->reveal());
$this->assertContains($header, HeaderStack::getHeaders());
}
public function headerProvider()
{
return [
["Content-length: 2048"],
["X-foo: bar"],
["X-foo: baz"]
];
}
public function testOutputsBody()
{
$content = "Hello, world!";
$this->body->isReadable()->willReturn(true);
$this->body->__toString()->willReturn($content);
$responder = new Responder();
ob_start();
$responder->respond($this->response->reveal());
$captured = ob_get_contents();
ob_end_clean();
$this->assertEquals($content, $captured);
}
public function testOutputsBodyInChunks()
{
$content = "Hello, world!";
$chunkSize = 3;
$position = 0;
$this->body->isReadable()->willReturn(true);
$this->body->rewind()->willReturn(true);
$this->body->eof()->willReturn(false);
$this->body->read(Argument::any())->will(function ($args) use ($content, &$position) {
$chunkSize = $args[0];
$chunk = substr($content, $position, $chunkSize);
$position += $chunkSize;
if ($position >= strlen($content)) {
$this->eof()->willReturn(true);
}
return $chunk;
});
$responder = new Responder();
ob_start();
$responder->respond($this->response->reveal(), $chunkSize);
$captured = ob_get_contents();
ob_end_clean();
$this->assertEquals($content, $captured);
}
}