From de46c8e0896b625ca650c7a3f31374b0f8c186af Mon Sep 17 00:00:00 2001 From: PJ Dietz Date: Mon, 25 Jun 2018 15:46:37 -0400 Subject: [PATCH] Rework Server to be configured with setters --- src/Routing/Router.php | 2 +- src/Server.php | 274 +++++++++--------- test/tests/integration/RoutingTest.php | 15 +- .../unit/Routing/Route/RouteFactoryTest.php | 11 +- test/tests/unit/ServerTest.php | 186 ++++++------ 5 files changed, 236 insertions(+), 252 deletions(-) diff --git a/src/Routing/Router.php b/src/Routing/Router.php index 1bf8a50..35bc5b9 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -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 = []; diff --git a/src/Server.php b/src/Server.php index 77d411e..cb2c0a3 100644 --- a/src/Server.php +++ b/src/Server.php @@ -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; + } } diff --git a/test/tests/integration/RoutingTest.php b/test/tests/integration/RoutingTest.php index 9e9d2de..18818cd 100644 --- a/test/tests/integration/RoutingTest.php +++ b/test/tests/integration/RoutingTest.php @@ -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; } diff --git a/test/tests/unit/Routing/Route/RouteFactoryTest.php b/test/tests/unit/Routing/Route/RouteFactoryTest.php index 63beaee..38b5e8b 100644 --- a/test/tests/unit/Routing/Route/RouteFactoryTest.php +++ b/test/tests/unit/Routing/Route/RouteFactoryTest.php @@ -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()); } } diff --git a/test/tests/unit/ServerTest.php b/test/tests/unit/ServerTest.php index 4ed4927..4aed4d7 100644 --- a/test/tests/unit/ServerTest.php +++ b/test/tests/unit/ServerTest.php @@ -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); } }