Add DispatchStack

This commit is contained in:
PJ Dietz 2015-05-10 10:30:22 -04:00
parent 2adcbd8636
commit 560b1e8ff0
3 changed files with 238 additions and 0 deletions

View File

@ -0,0 +1,91 @@
<?php
namespace WellRESTed\Routing;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class DispatchStack implements DispatchStackInterface
{
private $stack;
private $dispatcher;
public function __construct()
{
$this->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;
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace WellRESTed\Routing;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
interface DispatchStackInterface extends MiddlewareInterface
{
/**
* Push a new middleware onto the stack.
*
* @param mixed $middleware Middleware to dispatch in sequence
* @return self
*/
public function add($middleware);
/**
* 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);
}

View File

@ -0,0 +1,109 @@
<?php
namespace WellRESTed\Test\Unit\Routing;
use Prophecy\Argument;
use WellRESTed\Routing\DispatchStack;
/**
* @coversDefaultClass WellRESTed\Routing\DispatchStack
* @uses WellRESTed\Routing\DispatchStack
* @uses WellRESTed\Routing\Dispatcher
*/
class DispatchStackTest extends \PHPUnit_Framework_TestCase
{
private $request;
private $response;
private $next;
public function setUp()
{
parent::setUp();
$this->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);
}
}