Begin RouteMap
This commit is contained in:
parent
66319218cb
commit
d5eb044169
|
|
@ -2,30 +2,10 @@
|
|||
|
||||
namespace WellRESTed\Routing\Route;
|
||||
|
||||
use WellRESTed\Routing\RouteTableInterface;
|
||||
|
||||
interface RouteFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Adds a new route to a route table.
|
||||
*
|
||||
* This method SHOULD parse $target to determine to the type of route to
|
||||
* use and MUST create the route with the provided $middleware.
|
||||
*
|
||||
* Once the implementation has created the route the route, it MUST
|
||||
* the route with $routeTable by calling an appropriate RouteTable::add-
|
||||
* method.
|
||||
*
|
||||
* $extra MAY be passed to route constructors that use an extra option,
|
||||
* such as TemplateRoute.
|
||||
*
|
||||
* This method MAY register any instance implementing
|
||||
* WellRESTed\Routing\Route\RouteInterface.
|
||||
*
|
||||
* @param RouteTableInterface $routeTable Table to add the route to
|
||||
* @param string $target Path, prefix, or pattern to match
|
||||
* @param mixed $middleware Middleware to dispatch
|
||||
* @param mixed $extra Additional options to pass to a route constructor
|
||||
* Creates a route for the given target.
|
||||
*/
|
||||
public function registerRoute(RouteTableInterface $routeTable, $target, $middleware, $extra = null);
|
||||
public function create($target);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,26 @@
|
|||
|
||||
namespace WellRESTed\Routing\Route;
|
||||
|
||||
use WellRESTed\Routing\MethodMapInterface;
|
||||
use WellRESTed\Routing\MiddlewareInterface;
|
||||
|
||||
interface RouteInterface extends MiddlewareInterface
|
||||
{
|
||||
const TYPE_STATIC = 0;
|
||||
const TYPE_PREFIX = 1;
|
||||
const TYPE_PATTERN = 2;
|
||||
|
||||
public function getTarget();
|
||||
|
||||
public function getType();
|
||||
|
||||
/**
|
||||
* Return the instance mapping methods to middleware for this route.
|
||||
*
|
||||
* @return MethodMapInterface
|
||||
*/
|
||||
public function getMethodMap();
|
||||
|
||||
/**
|
||||
* Examines a path (request target) and returns whether or not the route
|
||||
* should handle the request providing the target.
|
||||
|
|
|
|||
|
|
@ -4,9 +4,32 @@ namespace WellRESTed\Routing;
|
|||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use WellRESTed\Routing\Route\RouteFactory;
|
||||
use WellRESTed\Routing\Route\RouteFactoryInterface;
|
||||
use WellRESTed\Routing\Route\RouteInterface;
|
||||
|
||||
class RouteMap implements RouteMapInterface
|
||||
{
|
||||
/** @var RouteFactoryInterface */
|
||||
private $factory;
|
||||
/** @var RouteInterface[] Array of Route objects */
|
||||
private $routes;
|
||||
/** @var RouteInterface[] Hash array mapping exact paths to routes */
|
||||
private $staticRoutes;
|
||||
/** @var RouteInterface[] Hash array mapping path prefixes to routes */
|
||||
private $prefixRoutes;
|
||||
/** @var RouteInterface[] Hash array mapping path prefixes to routes */
|
||||
private $patternRoutes;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->factory = $this->getRouteFactory();
|
||||
$this->routes = [];
|
||||
$this->staticRoutes = [];
|
||||
$this->prefixRoutes = [];
|
||||
$this->patternRoutes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register middleware with the router for a given path and method.
|
||||
*
|
||||
|
|
@ -36,11 +59,102 @@ class RouteMap implements RouteMapInterface
|
|||
*/
|
||||
public function add($method, $target, $middleware)
|
||||
{
|
||||
// TODO: Implement addRoute() method.
|
||||
$route = $this->getRouteForTarget($target);
|
||||
$route->getMethodMap()->setMethod($method, $middleware);
|
||||
}
|
||||
|
||||
public function dispatch(ServerRequestInterface $request, ResponseInterface &$response)
|
||||
{
|
||||
// TODO: Implement dispatch() method.
|
||||
$requestTarget = $request->getRequestTarget();
|
||||
|
||||
$route = $this->getStaticRoute($requestTarget);
|
||||
if ($route) {
|
||||
$route->dispatch($request, $response);
|
||||
return;
|
||||
}
|
||||
|
||||
$route = $this->getPrefixRoute($requestTarget);
|
||||
if ($route) {
|
||||
$route->dispatch($request, $response);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RouteFactoryInterface
|
||||
*/
|
||||
protected function getRouteFactory()
|
||||
{
|
||||
return new RouteFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the route for a given target.
|
||||
*
|
||||
* @param $target
|
||||
* @return RouteInterface
|
||||
*/
|
||||
private function getRouteForTarget($target)
|
||||
{
|
||||
if (isset($this->routes[$target])) {
|
||||
$route = $this->routes[$target];
|
||||
} else {
|
||||
$route = $this->factory->create($target);
|
||||
$this->registerRouteForTarget($route, $target);
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
|
||||
private function registerRouteForTarget($route, $target)
|
||||
{
|
||||
// Store the route to the hash indexed by original target.
|
||||
$this->routes[$target] = $route;
|
||||
|
||||
// Store the route to the array of routes for its type.
|
||||
switch ($route->getType()) {
|
||||
case RouteInterface::TYPE_STATIC:
|
||||
$this->staticRoutes[$route->getTarget()] = $route;
|
||||
break;
|
||||
case RouteInterface::TYPE_PREFIX:
|
||||
$this->prefixRoutes[rtrim($route->getTarget(), "*")] = $route;
|
||||
break;
|
||||
case RouteInterface::TYPE_PATTERN:
|
||||
$this->patternRoutes[] = $route;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function getStaticRoute($requestTarget)
|
||||
{
|
||||
if (isset($this->staticRoutes[$requestTarget])) {
|
||||
return $this->staticRoutes[$requestTarget];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getPrefixRoute($requestTarget)
|
||||
{
|
||||
// Find all prefixes that match the start of this path.
|
||||
$prefixes = array_keys($this->prefixRoutes);
|
||||
$matches = array_filter(
|
||||
$prefixes,
|
||||
function ($prefix) use ($requestTarget) {
|
||||
return (strrpos($requestTarget, $prefix, -strlen($requestTarget)) !== false);
|
||||
}
|
||||
);
|
||||
|
||||
if ($matches) {
|
||||
if (count($matches) > 0) {
|
||||
// If there are multiple matches, sort them to find the one with the longest string length.
|
||||
$compareByLength = function ($a, $b) {
|
||||
return strlen($b) - strlen($a);
|
||||
};
|
||||
usort($matches, $compareByLength);
|
||||
}
|
||||
$route = $this->prefixRoutes[$matches[0]];
|
||||
return $route;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace WellRESTed\Test\Unit\Routing;
|
||||
|
||||
use Prophecy\Argument;
|
||||
use WellRESTed\Routing\Route\RouteInterface;
|
||||
use WellRESTed\Routing\RouteMap;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass WellRESTed\Routing\RouteMap
|
||||
* @uses WellRESTed\Routing\RouteMap
|
||||
*/
|
||||
class RouteMapTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $methodMap;
|
||||
private $factory;
|
||||
private $request;
|
||||
private $response;
|
||||
private $route;
|
||||
private $routeMap;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->methodMap = $this->prophesize('WellRESTed\Routing\MethodMapInterface');
|
||||
$this->methodMap->setMethod(Argument::cetera());
|
||||
|
||||
$this->route = $this->prophesize('WellRESTed\Routing\Route\RouteInterface');
|
||||
$this->route->dispatch(Argument::cetera())->willReturn();
|
||||
$this->route->getMethodMap()->willReturn($this->methodMap->reveal());
|
||||
$this->route->getType()->willReturn(RouteInterface::TYPE_STATIC);
|
||||
$this->route->getTarget()->willReturn("/");
|
||||
|
||||
$this->factory = $this->prophesize('WellRESTed\Routing\Route\RouteFactory');
|
||||
$this->factory->create(Argument::any())->willReturn($this->route->reveal());
|
||||
|
||||
$this->request = $this->prophesize('Psr\Http\Message\ServerRequestInterface');
|
||||
|
||||
$this->response = $this->prophesize('Psr\Http\Message\ResponseInterface');
|
||||
|
||||
$this->routeMap = $this->getMockBuilder('WellRESTed\Routing\RouteMap')
|
||||
->setMethods(["getRouteFactory"])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->routeMap->expects($this->any())
|
||||
->method("getRouteFactory")
|
||||
->will($this->returnValue($this->factory->reveal()));
|
||||
$this->routeMap->__construct();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Construction
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @covers ::getRouteFactory
|
||||
*/
|
||||
public function testCreatesInstance()
|
||||
{
|
||||
$routeMap = new RouteMap();
|
||||
$this->assertNotNull($routeMap);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Populating
|
||||
|
||||
/**
|
||||
* @covers ::add
|
||||
* @covers ::getRouteForTarget
|
||||
* @covers ::registerRouteForTarget
|
||||
*/
|
||||
public function testAddCreatesRouteForTarget()
|
||||
{
|
||||
$this->routeMap->add("GET", "/", "middleware");
|
||||
$this->factory->create("/")->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::add
|
||||
* @covers ::getRouteForTarget
|
||||
*/
|
||||
public function testAddDoesNotRecreateRouteForExistingTarget()
|
||||
{
|
||||
$this->routeMap->add("GET", "/", "middleware");
|
||||
$this->routeMap->add("POST", "/", "middleware");
|
||||
$this->factory->create("/")->shouldHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::add
|
||||
*/
|
||||
public function testAddPassesMethodAndMiddlewareToMethodMap()
|
||||
{
|
||||
$this->routeMap->add("GET", "/", "middleware");
|
||||
$this->methodMap->setMethod("GET", "middleware")->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Dispatching
|
||||
|
||||
/**
|
||||
* @covers ::dispatch
|
||||
* @covers ::getStaticRoute
|
||||
* @covers ::registerRouteForTarget
|
||||
*/
|
||||
public function testDispatchesStaticRoute()
|
||||
{
|
||||
$target = "/";
|
||||
|
||||
$this->request->getRequestTarget()->willReturn($target);
|
||||
$this->route->getTarget()->willReturn($target);
|
||||
$this->route->getType()->willReturn(RouteInterface::TYPE_STATIC);
|
||||
|
||||
$this->routeMap->add("GET", $target, "middleware");
|
||||
|
||||
$request = $this->request->reveal();
|
||||
$response = $this->response->reveal();
|
||||
$this->routeMap->dispatch($request, $response);
|
||||
|
||||
$this->route->dispatch(Argument::cetera())->shouldHaveBeenCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::dispatch
|
||||
* @covers ::getPrefixRoute
|
||||
* @covers ::registerRouteForTarget
|
||||
*/
|
||||
public function testDispatchesPrefixRoute()
|
||||
{
|
||||
$target = "/*";
|
||||
|
||||
$this->request->getRequestTarget()->willReturn($target);
|
||||
$this->route->getTarget()->willReturn($target);
|
||||
$this->route->getType()->willReturn(RouteInterface::TYPE_PREFIX);
|
||||
|
||||
$this->routeMap->add("GET", $target, "middleware");
|
||||
|
||||
$request = $this->request->reveal();
|
||||
$response = $this->response->reveal();
|
||||
$this->routeMap->dispatch($request, $response);
|
||||
|
||||
$this->route->dispatch(Argument::cetera())->shouldHaveBeenCalled();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue