diff --git a/src/Dispatching/DispatchStack.php b/src/Dispatching/DispatchStack.php index 59879d4..d4c8faf 100644 --- a/src/Dispatching/DispatchStack.php +++ b/src/Dispatching/DispatchStack.php @@ -5,6 +5,9 @@ namespace WellRESTed\Dispatching; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +/** + * Dispatches an ordered sequence of middleware. + */ class DispatchStack implements DispatchStackInterface { private $stack; @@ -22,8 +25,6 @@ class DispatchStack implements DispatchStackInterface /** * 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 */ @@ -36,17 +37,17 @@ class DispatchStack implements DispatchStackInterface /** * 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. + * The first middleware that was added is dispatched first. * - * 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. + * Each middleware, when dispatched, receives a $next callable that, when + * called, will dispatch the next middleware in the sequence. * - * If the instance is dispatched with no middleware added, the instance - * MUST call $next passing $request and $response and return the returned - * response. + * When the stack is dispatched empty, or when all middleware in the stack + * call the $next argument they were passed, this method will call the + * $next it receieved. + * + * When any middleware in the stack returns a response without calling its + * $next, the stack will not call the $next it received. * * @param ServerRequestInterface $request * @param ResponseInterface $response @@ -54,20 +55,16 @@ class DispatchStack implements DispatchStackInterface * @return ResponseInterface */ public function dispatch(ServerRequestInterface $request, ResponseInterface $response, $next) - { - $chain = $this->getCallableChain(); - $response = $chain($request, $response); - return $next($request, $response); - } - - // ------------------------------------------------------------------------ - - private function getCallableChain() { $dispatcher = $this->dispatcher; - // No-op function to use as the final middleware's $mext. - $next = function ($request, $response) { + // This flag will be set to true when the last middleware calls $next. + $stackCompleted = false; + + // The final middleware's $next returns $response unchanged and sets + // the $stackCompleted flag to indicate the stack has completed. + $chain = function ($request, $response) use (&$stackCompleted) { + $stackCompleted = true; return $response; }; @@ -77,11 +74,17 @@ class DispatchStack implements DispatchStackInterface // 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); + $chain = function ($request, $response) use ($dispatcher, $middleware, $chain) { + return $dispatcher->dispatch($middleware, $request, $response, $chain); }; } - return $next; + $response = $chain($request, $response); + + if ($stackCompleted) { + return $next($request, $response); + } else { + return $response; + } } } diff --git a/src/Dispatching/DispatchStackInterface.php b/src/Dispatching/DispatchStackInterface.php index af20cc6..c878bf7 100644 --- a/src/Dispatching/DispatchStackInterface.php +++ b/src/Dispatching/DispatchStackInterface.php @@ -6,11 +6,16 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use WellRESTed\MiddlewareInterface; +/** + * Dispatches an ordered sequence of middleware. + */ interface DispatchStackInterface extends MiddlewareInterface { /** * 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 */ @@ -19,16 +24,24 @@ interface DispatchStackInterface extends MiddlewareInterface /** * 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. + * The first middleware added to the stack MUST be dispatched first. * - * 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 + * Each middleware, when dispatched, MUST receive a $next callable that + * dispatches the middleware that follows it, unless it is the last + * middleware. The last middleware MUST receive a $next callable that * 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. + * When any middleware does not call the $next argument it recieved, the + * stack instance MUST stop propogating through the stack and MUST return + * the response without calling the $next argument passed to dispatch. + * + * This method MUST call the passed $next argument when: + * - The stack is empty (i.e., there is no middleware to dispatch) + * - Each middleware called the $next that it receieved. + * + * This method MUST NOT call the passed $next argument when the stack is + * not empty and any middleware returns a response without calling the + * $next it receieved. * * @param ServerRequestInterface $request * @param ResponseInterface $response diff --git a/test/tests/unit/Dispatching/DispatchStackTest.php b/test/tests/unit/Dispatching/DispatchStackTest.php index 23637cc..04957c7 100644 --- a/test/tests/unit/Dispatching/DispatchStackTest.php +++ b/test/tests/unit/Dispatching/DispatchStackTest.php @@ -52,7 +52,6 @@ class DispatchStackTest extends \PHPUnit_Framework_TestCase /** * @covers ::dispatch - * @covers ::getCallableChain */ public function testDispachesMiddlewareInOrderAdded() { @@ -76,6 +75,22 @@ class DispatchStackTest extends \PHPUnit_Framework_TestCase $this->assertEquals(["first", "second", "third"], $callOrder); } + /** + * @covers ::dispatch + */ + public function testCallsNextAfterDispatchingEmptyStack() + { + $nextCalled = false; + $next = function ($request, $response) use (&$nextCalled) { + $nextCalled = true; + return $response; + }; + + $stack = new DispatchStack($this->dispatcher->reveal()); + $stack->dispatch($this->request->reveal(), $this->response->reveal(), $next); + $this->assertTrue($nextCalled); + } + /** * @covers ::dispatch */ @@ -103,7 +118,7 @@ class DispatchStackTest extends \PHPUnit_Framework_TestCase /** * @covers ::dispatch */ - public function testCallsNextAfterDispatchingEmptyStack() + public function testDoesNotCallNextWhenStackStopsEarly() { $nextCalled = false; $next = function ($request, $response) use (&$nextCalled) { @@ -111,8 +126,19 @@ class DispatchStackTest extends \PHPUnit_Framework_TestCase return $response; }; + $middlewareGo = function ($request, $response, $next) use (&$callOrder) { + return $next($request, $response); + }; + $middlewareStop = function ($request, $response, $next) use (&$callOrder) { + return $response; + }; + $stack = new DispatchStack($this->dispatcher->reveal()); + $stack->add($middlewareGo); + $stack->add($middlewareStop); + $stack->add($middlewareStop); + $stack->dispatch($this->request->reveal(), $this->response->reveal(), $next); - $this->assertTrue($nextCalled); + $this->assertFalse($nextCalled); } }