From 560b1e8ff037809ca757e22ff78aaf6815323237 Mon Sep 17 00:00:00 2001 From: PJ Dietz Date: Sun, 10 May 2015 10:30:22 -0400 Subject: [PATCH] Add DispatchStack --- src/Routing/DispatchStack.php | 91 +++++++++++++++ src/Routing/DispatchStackInterface.php | 38 ++++++ test/tests/unit/Routing/DispatchStackTest.php | 109 ++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 src/Routing/DispatchStack.php create mode 100644 src/Routing/DispatchStackInterface.php create mode 100644 test/tests/unit/Routing/DispatchStackTest.php diff --git a/src/Routing/DispatchStack.php b/src/Routing/DispatchStack.php new file mode 100644 index 0000000..6a49d51 --- /dev/null +++ b/src/Routing/DispatchStack.php @@ -0,0 +1,91 @@ +dispatcher = $this->getDispatcher(); + $this->stack = []; + } + + /** + * Push a new middleware onto the stack. + * + * This method MUST preserve the order in which middleware added. + * + * @param mixed $middleware Middleware to dispatch in sequence + * @return self + */ + public function add($middleware) + { + $this->stack[] = $middleware; + return $this; + } + + /** + * Dispatch the contained middleware in the order in which they were added. + * + * The first middleware added to the stack MUST be the first to be + * dispatched. + * + * Each middleware, when dispatched, MUST 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) + { + $chain = $this->getCallableChain(); + $response = $chain($request, $response); + return $next($request, $response); + } + + // ------------------------------------------------------------------------ + + protected function getDispatcher() + { + return new Dispatcher(); + } + + // ------------------------------------------------------------------------ + + private function getCallableChain() + { + $dispatcher = $this->dispatcher; + + // No-op function to use as the final middleware's $mext. + $next = function ($request, $response) { + return $response; + }; + + // Create a chain of callables. + // + // Each callable wil take $request and $response parameters, and will + // contain a dispatcher, the associated middleware, and a $next + // that is the links to the next middleware in the chain. + foreach (array_reverse($this->stack) as $middleware) { + $next = function ($request, $response) use ($dispatcher, $middleware, $next) { + return $dispatcher->dispatch($middleware, $request, $response, $next); + }; + } + + return $next; + } +} diff --git a/src/Routing/DispatchStackInterface.php b/src/Routing/DispatchStackInterface.php new file mode 100644 index 0000000..f516f16 --- /dev/null +++ b/src/Routing/DispatchStackInterface.php @@ -0,0 +1,38 @@ +request = $this->prophesize('Psr\Http\Message\ServerRequestInterface'); + $this->response = $this->prophesize('Psr\Http\Message\ResponseInterface'); + $this->next = function ($request, $response) { + return $response; + }; + } + + /** + * @covers ::__construct + * @covers ::getDispatcher + */ + public function testCreatesInstance() + { + $stack = new DispatchStack(); + $this->assertNotNull($stack); + } + + /** + * @covers ::add + */ + public function testAddIsFluid() + { + $stack = new DispatchStack(); + $this->assertSame($stack, $stack->add("middleware1")); + } + + /** + * @covers ::dispatch + */ + public function testDispachesMiddlewareInOrderAdded() + { + // Each middelware will add its "name" to this array. + $callOrder = []; + + $stack = new DispatchStack(); + $stack->add(function ($request, $response, $next) use (&$callOrder) { + $callOrder[] = "first"; + return $next($request, $response); + }); + $stack->add(function ($request, $response, $next) use (&$callOrder) { + $callOrder[] = "second"; + return $next($request, $response); + }); + $stack->add(function ($request, $response, $next) use (&$callOrder) { + $callOrder[] = "third"; + return $next($request, $response); + }); + $stack->dispatch($this->request->reveal(), $this->response->reveal(), $this->next); + $this->assertEquals(["first", "second", "third"], $callOrder); + } + + public function testCallsNextAfterDispatchingStack() + { + $nextCalled = false; + $next = function ($request, $response) use (&$nextCalled) { + $nextCalled = true; + return $response; + }; + + $middleware = function ($request, $response, $next) use (&$callOrder) { + return $next($request, $response); + }; + + $stack = new DispatchStack(); + $stack->add($middleware); + $stack->add($middleware); + $stack->add($middleware); + + $stack->dispatch($this->request->reveal(), $this->response->reveal(), $next); + $this->assertTrue($nextCalled); + } + + /** + * @covers ::dispatch + */ + public function testCallsNextAfterDispatchingEmptyStack() + { + $nextCalled = false; + $next = function ($request, $response) use (&$nextCalled) { + $nextCalled = true; + return $response; + }; + + $stack = new DispatchStack(); + $stack->dispatch($this->request->reveal(), $this->response->reveal(), $next); + $this->assertTrue($nextCalled); + } +}