DispatchStack calls $next only when the stack runs to the end.

This commit is contained in:
PJ Dietz 2015-05-12 07:54:11 -04:00
parent 26a6a25d3b
commit 297e985e84
3 changed files with 77 additions and 35 deletions

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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);
}
}