Rework Server to be configured with setters

This commit is contained in:
PJ Dietz 2018-06-25 15:46:37 -04:00
parent be3d007961
commit de46c8e089
5 changed files with 236 additions and 252 deletions

View File

@ -48,7 +48,7 @@ class Router implements RouterInterface
*/
public function __construct($dispatcher = null, $pathVariablesAttributeName = null)
{
$this->dispatcher = $dispatcher ?? $this->getDefaultDispatcher();
$this->dispatcher = $dispatcher ?: $this->getDefaultDispatcher();
$this->pathVariablesAttributeName = $pathVariablesAttributeName;
$this->factory = $this->getRouteFactory($this->dispatcher);
$this->routes = [];

View File

@ -16,50 +16,22 @@ class Server
{
/** @var array */
protected $attributes;
/** @var string ServerRequestInterface attribute name for matched path variables */
protected $pathVariablesAttributeName;
/** @var mixed[] List array of middleware */
protected $stack;
/** @var DispatcherInterface */
private $dispatcher;
/** @var string ServerRequestInterface attribute name for matched path variables */
private $pathVariablesAttributeName;
/** @var ServerRequestInterface */
private $request;
/** @var ResponseInterface */
private $response;
/** @var TransmitterInterface */
private $transmitter;
/** @var mixed[] List array of middleware */
private $stack;
/** @var ResponseInterface */
private $unhandledResponse;
/**
* Create a new server.
*
* By default, when a route containing path variables matches, the path
* variables are stored individually as attributes on the
* ServerRequestInterface.
*
* When $pathVariablesAttributeName is set, a single attribute will be
* stored with the name. The value will be an array containing all of the
* path variables.
*
* @param array $attributes
* Key-value pairs to register as attributes with the server request.
* @param DispatcherInterface $dispatcher
* Dispatches handlers and middleware. If no object is passed, the
* Server will create a WellRESTed\Dispatching\Dispatcher.
* @param string|null $pathVariablesAttributeName
* Attribute name for matched path variables. A null value sets
* attributes directly.
*/
public function __construct(
array $attributes = null,
DispatcherInterface $dispatcher = null,
$pathVariablesAttributeName = null
) {
if ($attributes === null) {
$attributes = [];
}
$this->attributes = $attributes;
if ($dispatcher === null) {
$dispatcher = $this->getDefaultDispatcher();
}
$this->dispatcher = $dispatcher;
$this->pathVariablesAttributeName = $pathVariablesAttributeName;
public function __construct() {
$this->stack = [];
}
@ -67,7 +39,7 @@ class Server
* Push a new middleware onto the stack.
*
* @param mixed $middleware Middleware to dispatch in sequence
* @return static
* @return Server
*/
public function add($middleware)
{
@ -75,32 +47,6 @@ class Server
return $this;
}
/**
* Dispatch the contained middleware in the order in which they were added.
*
* The first middleware added to the stack is the first to be dispatched.
*
* Each middleware, when dispatched, will receive a $next callable that
* dispatches the middleware that follows it. The only exception to this is
* the last middleware in the stack which much receive a $next callable the
* returns the response unchanged.
*
* If the instance is dispatched with no middleware added, the instance
* MUST call $next passing $request and $response and return the returned
* response.
*
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param callable $next
* @return ResponseInterface
*/
public function dispatch(ServerRequestInterface $request, ResponseInterface $response, $next)
{
return $this->dispatcher->dispatch($this->stack, $request, $response, $next);
}
// ------------------------------------------------------------------------
/**
* Return a new Router that uses the server's dispatcher.
*
@ -108,114 +54,158 @@ class Server
*/
public function createRouter()
{
return new Router($this->getDispatcher(), $this->pathVariablesAttributeName);
}
/**
* Return the dispatched used by the server.
*
* @return DispatcherInterface
*/
public function getDispatcher()
{
return $this->dispatcher;
return new Router(
$this->getDispatcher(),
$this->pathVariablesAttributeName
);
}
/**
* Perform the request-response cycle.
*
* This method reads a server request, dispatches the request through the
* server's stack of middleware, and outputs the response.
*
* @param ServerRequestInterface $request Request provided by the client
* @param ResponseInterface $response Initial starting place response to
* propagate to middleware.
* @param TransmitterInterface $transmitter Instance to outputting the
* final response to the client.
* server's stack of middleware, and outputs the response via a Transmitter.
*/
public function respond(
ServerRequestInterface $request = null,
ResponseInterface $response = null,
TransmitterInterface $transmitter = null
) {
if ($request === null) {
$request = $this->getRequest();
}
foreach ($this->attributes as $name => $value) {
public function respond()
{
$request = $this->getRequest();
foreach ($this->getAttributes() as $name => $value) {
$request = $request->withAttribute($name, $value);
}
if ($response === null) {
$response = $this->getResponse();
}
if ($transmitter === null) {
$transmitter = $this->getTransmitter();
}
$response = $this->getResponse();
$next = function () {
return $this->getUnhandledResponse();
};
$response = $this->dispatch($request, $response, $next);
$dispatcher = $this->getDispatcher();
$response = $dispatcher->dispatch(
$this->stack, $request, $response, $next);
$transmitter = $this->getTransmitter();
$transmitter->transmit($request, $response);
}
// ------------------------------------------------------------------------
// The following method provide instances using default classes. To use
// custom classes, subclass Server and override methods as needed.
// -------------------------------------------------------------------------
/* Configuration */
/**
* Return an instance to dispatch middleware.
*
* @return DispatcherInterface
* @param array $attributes
* @return Server
*/
protected function getDefaultDispatcher()
public function setAttributes(array $attributes): Server
{
return new Dispatcher();
}
// @codeCoverageIgnoreStart
/**
* Return an instance representing the request submitted to the server.
*
* @return ServerRequestInterface
*/
protected function getRequest()
{
return ServerRequest::getServerRequest();
$this->attributes = $attributes;
return $this;
}
/**
* Return an instance that will output the response to the client.
*
* @return TransmitterInterface
* @param DispatcherInterface $dispatcher
* @return Server
*/
protected function getTransmitter()
public function setDispatcher(DispatcherInterface $dispatcher): Server
{
return new Transmitter();
$this->dispatcher = $dispatcher;
return $this;
}
/**
* Return a "blank" response instance to populate.
*
* The response will be dispatched through the middleware.
*
* @return ResponseInterface
* @param string $name
* @return Server
*/
protected function getResponse()
{
return new Response();
public function setPathVariablesAttributeName(string $name): Server {
$this->pathVariablesAttributeName = $name;
return $this;
}
/**
* Return a Response to indicate the server was unable to find any handlers
* that handled the request. By default, this is a 404 error.
*
* @return ResponseInterface
* @param ServerRequestInterface $request
* @return Server
*/
protected function getUnhandledResponse(): ResponseInterface
public function setRequest(ServerRequestInterface $request): Server
{
return new Response(404);
$this->request = $request;
return $this;
}
// @codeCoverageIgnoreEnd
/**
* @param ResponseInterface $response
* @return Server
*/
public function setResponse(ResponseInterface $response): Server
{
$this->response = $response;
return $this;
}
/**
* @param TransmitterInterface $transmitter
* @return Server
*/
public function setTransmitter(TransmitterInterface $transmitter): Server
{
$this->transmitter = $transmitter;
return $this;
}
/**
* @param ResponseInterface $response
* @return Server
*/
public function setUnhandledResponse(ResponseInterface $response): Server {
$this->unhandledResponse = $response;
return $this;
}
// -------------------------------------------------------------------------
/* Defaults */
private function getAttributes()
{
if (!$this->attributes) {
$this->attributes = [];
}
return $this->attributes;
}
private function getDispatcher()
{
if (!$this->dispatcher) {
$this->dispatcher = new Dispatcher();
}
return $this->dispatcher;
}
private function getRequest()
{
if (!$this->request) {
$this->request = ServerRequest::getServerRequest();
}
return $this->request;
}
private function getResponse()
{
if (!$this->response) {
$this->response = new Response();
}
return $this->response;
}
private function getTransmitter()
{
if (!$this->transmitter) {
$this->transmitter = new Transmitter();
}
return $this->transmitter;
}
private function getUnhandledResponse()
{
if (!$this->unhandledResponse) {
$this->unhandledResponse = new Response(404);
}
return $this->unhandledResponse;
}
}

View File

@ -13,9 +13,7 @@ use WellRESTed\Server;
use WellRESTed\Test\TestCase;
use WellRESTed\Transmission\TransmitterInterface;
/**
* @coversNothing
*/
/** @coversNothing */
class RoutingTest extends TestCase
{
/** @var Server */
@ -30,15 +28,22 @@ class RoutingTest extends TestCase
public function setUp()
{
parent::setUp();
$this->server = new Server();
$this->transmitter = new TransmitterMock();
$this->request = new ServerRequest();
$this->response = new Response();
$this->server = new Server();
$this->server->setTransmitter($this->transmitter);
}
private function respond(): ResponseInterface
{
$this->server->respond($this->request, $this->response, $this->transmitter);
$this->server->setRequest($this->request);
$this->server->setResponse($this->response);
$this->server->respond();
return $this->transmitter->response;
}

View File

@ -2,6 +2,7 @@
namespace WellRESTed\Test\Unit\Routing\Route;
use WellRESTed\Dispatching\DispatcherInterface;
use WellRESTed\Routing\Route\RouteFactory;
use WellRESTed\Routing\Route\RouteInterface;
use WellRESTed\Test\TestCase;
@ -12,34 +13,34 @@ class RouteFactoryTest extends TestCase
public function setUp()
{
$this->dispatcher = $this->prophesize('WellRESTed\Dispatching\DispatcherInterface');
$this->dispatcher = $this->prophesize(DispatcherInterface::class);
}
public function testCreatesStaticRoute()
{
$factory = new RouteFactory($this->dispatcher->reveal());
$route = $factory->create("/cats/");
$route = $factory->create('/cats/');
$this->assertSame(RouteInterface::TYPE_STATIC, $route->getType());
}
public function testCreatesPrefixRoute()
{
$factory = new RouteFactory($this->dispatcher->reveal());
$route = $factory->create("/cats/*");
$route = $factory->create('/cats/*');
$this->assertSame(RouteInterface::TYPE_PREFIX, $route->getType());
}
public function testCreatesRegexRoute()
{
$factory = new RouteFactory($this->dispatcher->reveal());
$route = $factory->create("~/cat/[0-9]+~");
$route = $factory->create('~/cat/[0-9]+~');
$this->assertSame(RouteInterface::TYPE_PATTERN, $route->getType());
}
public function testCreatesTemplateRoute()
{
$factory = new RouteFactory($this->dispatcher->reveal());
$route = $factory->create("/cat/{id}");
$route = $factory->create('/cat/{id}');
$this->assertSame(RouteInterface::TYPE_PATTERN, $route->getType());
}
}

View File

@ -3,18 +3,20 @@
namespace WellRESTed\Test\Unit;
use Prophecy\Argument;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use WellRESTed\Dispatching\DispatcherInterface;
use WellRESTed\Message\Response;
use WellRESTed\Message\ServerRequest;
use WellRESTed\Message\Stream;
use WellRESTed\Server;
use WellRESTed\Test\TestCase;
use WellRESTed\Transmission\TransmitterInterface;
require_once __DIR__ . '/../../src/HeaderStack.php';
class ServerTest extends TestCase
{
private $transmitter;
/** @var Server */
private $server;
public function setUp()
@ -25,23 +27,10 @@ class ServerTest extends TestCase
$this->transmitter->transmit(Argument::cetera())->willReturn();
$this->server = new Server();
$this->server->setTransmitter($this->transmitter->reveal());
}
private function respond()
{
$this->server->respond(
new ServerRequest(),
new Response(),
$this->transmitter->reveal()
);
}
// ------------------------------------------------------------------------
public function testReturnsDispatcher()
{
$this->assertNotNull($this->server->getDispatcher());
}
// -------------------------------------------------------------------------
public function testDispatchesMiddlewareStack()
{
@ -70,12 +59,42 @@ class ServerTest extends TestCase
}
);
$this->respond();
$this->server->respond();
$this->assertEquals(['first', 'second', 'third'], $steps);
}
// ------------------------------------------------------------------------
public function testDispatchedRequest()
{
$request = new ServerRequest();
$capturedRequest = null;
$this->server->setRequest($request);
$this->server->add(function ($rqst, $resp) use (&$capturedRequest) {
$capturedRequest = $rqst;
return $resp;
});
$this->server->respond();
$this->assertSame($request, $capturedRequest);
}
public function testDispatchedResponse()
{
$response = new Response();
$capturedResponse = null;
$this->server->setResponse($response);
$this->server->add(function ($rqst, $resp) use (&$capturedResponse) {
$capturedResponse = $resp;
return $resp;
});
$this->server->respond();
$this->assertSame($response, $capturedResponse);
}
// -------------------------------------------------------------------------
// Respond
public function testRespondSendsResponseToTransmitter()
@ -100,7 +119,7 @@ class ServerTest extends TestCase
}
);
$this->respond();
$this->server->respond();
$this->transmitter->transmit(
Argument::any(),
@ -108,7 +127,7 @@ class ServerTest extends TestCase
)->shouldHaveBeenCalled();
}
// ------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Router
public function testCreatesRouterWithDispatcher()
@ -121,7 +140,8 @@ class ServerTest extends TestCase
}
);
$server = new Server(null, $dispatcher->reveal());
$this->server->setDispatcher($dispatcher->reveal());
$this->server->setPathVariablesAttributeName('pathVariables');
$request = (new ServerRequest())
->withMethod("GET")
@ -131,7 +151,7 @@ class ServerTest extends TestCase
return $resp;
};
$router = $server->createRouter();
$router = $this->server->createRouter();
$router->register("GET", "/", "middleware");
$router($request, $response, $next);
@ -139,39 +159,32 @@ class ServerTest extends TestCase
->shouldHaveBeenCalled();
}
// ------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Attributes
public function testAddsAttributesToRequest()
{
$attributes = [
$this->server->setAttributes([
'name' => 'value'
];
]);
$server = new Server($attributes);
$spyMiddleware = function ($rqst, $resp) use (&$capturedRequest) {
$capturedRequest = null;
$this->server->add(function ($rqst, $resp) use (&$capturedRequest) {
$capturedRequest = $rqst;
return $resp;
};
});
$server->add($spyMiddleware);
$server->respond(
new ServerRequest(),
new Response(),
$this->transmitter->reveal()
);
$this->server->respond();
$this->assertEquals('value', $capturedRequest->getAttribute('name'));
}
// ------------------------------------------------------------------------
// -------------------------------------------------------------------------
// End of Stack
public function testRespondsWithDefaultHandlerWhenReachingEndOfStack()
public function testResponds404ByDefaultWhenReachingEndOfStack()
{
$this->respond();
$this->server->respond();
$has404StatusCode = function ($response) {
return $response->getStatusCode() === 404;
@ -183,69 +196,44 @@ class ServerTest extends TestCase
)->shouldHaveBeenCalled();
}
// ------------------------------------------------------------------------
// Defaults
public function testUsesDefaultRequestResponseAndTransmitter()
public function testRespondsWithUnhandledResponseWhenReachingEndOfStack()
{
$request = new ServerRequest();
$response = new Response();
$unhandledResponse = (new Response(404))
->withBody(new Stream("I can't find it!"));
$server = new TestServer(
$request,
$response,
$this->transmitter->reveal()
);
$server->add(function ($rqst, $resp) {
return $resp;
$this->server->setUnhandledResponse($unhandledResponse);
$this->server->respond();
$isExpectedResponse = function ($response) use ($unhandledResponse) {
return $response === $unhandledResponse;
};
$this->transmitter->transmit(
Argument::any(),
Argument::that($isExpectedResponse)
)->shouldHaveBeenCalled();
}
// -------------------------------------------------------------------------
public function testCreatesStockTransmitterByDefault()
{
$content = "Hello, world!";
$response = (new Response())
->withBody(new Stream($content));
$server = new Server();
$server->add(function () use ($response) {
return $response;
});
ob_start();
$server->respond();
$captured = ob_get_contents();
ob_end_clean();
$this->transmitter->transmit($request, $response)
->shouldHaveBeenCalled();
}
}
// ----------------------------------------------------------------------------
class TestServer extends Server
{
/** @var ServerRequestInterface */
private $request;
/** @var ResponseInterface */
private $response;
/** @var TransmitterInterface */
private $transmitter;
/**
* TestServer constructor.
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param TransmitterInterface $transmitter
*/
public function __construct(
ServerRequestInterface $request,
ResponseInterface $response,
TransmitterInterface $transmitter
) {
parent::__construct();
$this->request = $request;
$this->response = $response;
$this->transmitter = $transmitter;
}
protected function getRequest()
{
return $this->request;
}
protected function getResponse()
{
return $this->response;
}
protected function getTransmitter()
{
return $this->transmitter;
$this->assertEquals($content, $captured);
}
}