Store PrefixRoutes to a separate array.

Prioritize routes in the order static, prefix, everything else.
This commit is contained in:
PJ Dietz 2015-01-02 13:13:08 -05:00
parent caef817535
commit ca2c8625ec
4 changed files with 218 additions and 8 deletions

View File

@ -0,0 +1,28 @@
<?php
/**
* pjdietz\WellRESTed\Interfaces\Route\PrefixRouteInterface
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Interfaces\Routes;
interface PrefixRouteInterface
{
/**
* Returns the target class this maps to.
*
* @return string Fully qualified name for a HandlerInterface
*/
public function getHandler();
/**
* Returns the path prefixes this maps to a target handler.
*
* @return array Array of path prefixes.
*/
public function getPrefixes();
}

View File

@ -4,7 +4,7 @@
* pjdietz\WellRESTed\Router
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2014 by PJ Dietz
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
@ -14,6 +14,7 @@ use pjdietz\WellRESTed\Exceptions\HttpExceptions\HttpException;
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
use pjdietz\WellRESTed\Interfaces\Routes\PrefixRouteInterface;
use pjdietz\WellRESTed\Interfaces\Routes\StaticRouteInterface;
/**
@ -26,6 +27,9 @@ class Router implements HandlerInterface
/** @var array Array of Route objects */
private $routes;
/** @var array Hash array mapping path prefixes to handlers */
private $prefixRoutes;
/** @var array Hash array mapping exact paths to handlers */
private $staticRoutes;
@ -36,6 +40,7 @@ class Router implements HandlerInterface
public function __construct()
{
$this->routes = array();
$this->prefixRoutes = array();
$this->staticRoutes = array();
$this->errorHandlers = array();
}
@ -76,6 +81,8 @@ class Router implements HandlerInterface
{
if ($route instanceof StaticRouteInterface) {
$this->setStaticRoute($route->getPaths(), $route->getHandler());
} elseif ($route instanceof PrefixRouteInterface) {
$this->setPrefixRoute($route->getPrefixes(), $route->getHandler());
} else {
$this->routes[] = $route;
}
@ -96,7 +103,23 @@ class Router implements HandlerInterface
}
/**
* A route for an exact match to a path.
* Add a route for paths beginning with a given prefix.
*
* @param string|array $prefixes Prefix of a path to match
* @param string $handler Fully qualified name to an autoloadable handler class
*/
public function setPrefixRoute($prefixes, $handler)
{
if (is_string($prefixes)) {
$prefixes = array($prefixes);
}
foreach ($prefixes as $prefix) {
$this->prefixRoutes[$prefix] = $handler;
}
}
/**
* Add a route for an exact match to a path.
*
* @param string|array $paths Path component of the URI or a list of paths
* @param string $handler Fully qualified name to an autoloadable handler class
@ -163,14 +186,48 @@ class Router implements HandlerInterface
return $response;
}
/**
* Returning the matching static handler, or null if none match.
*
* @param $path string The request's path
* @return HandlerInterface|null
*/
private function getStaticHandler($path)
{
if (array_key_exists($path, $this->staticRoutes)) {
// Instantiate and return the handler identified by the path.
return new $this->staticRoutes[$path]();
}
return null;
}
/**
* Returning the best-matching prefix handler, or null if none match.
*
* @param $path string The request's path
* @return HandlerInterface|null
*/
private function getPrefixHandler($path)
{
// Find all prefixes that match the start of this path.
$prefixes = array_keys($this->prefixRoutes);
$matches = array_filter($prefixes, function ($prefix) use ($path) {
return (strrpos($path, $prefix, -strlen($path)) !== false);
});
if ($matches) {
// If there are multiple matches, sort them to find the one with the longest string length.
if (count($matches) > 0) {
usort($matches, function ($a, $b) {
return strlen($b) - strlen($a);
});
}
// Instantiate and return the handler identified as the best match.
return new $this->prefixRoutes[$matches[0]]();
}
return null;
}
private function getResponseFromRoutes(RequestInterface $request, array $args = null)
{
$response = null;
@ -183,6 +240,12 @@ class Router implements HandlerInterface
return $this->tryResponse($handler, $request, $args);
}
// Check prefix routes for any routes that match. Use the longest matching prefix.
$handler = $this->getPrefixHandler($path);
if ($handler) {
return $this->tryResponse($handler, $request, $args);
}
// Try each of the routes.
foreach ($this->routes as $route) {
/** @var HandlerInterface $route */

View File

@ -1,22 +1,46 @@
<?php
/**
* pjdietz\WellRESTed\PrefixRoute
* pjdietz\WellRESTed\Routes\PrefixRoute
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2014 by PJ Dietz
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Routes;
use InvalidArgumentException;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\Routes\PrefixRouteInterface;
/**
* Maps a list of static URI paths to a Handler
*/
class PrefixRoute extends StaticRoute
class PrefixRoute extends BaseRoute implements PrefixRouteInterface
{
/** @var array List of static URI paths */
private $prefixes;
/**
* Create a new StaticRoute for a given path or paths and a handler class.
*
* @param string|array $prefixes Path or list of paths the request must match
* @param string $targetClassName Fully qualified name to an autoloadable handler class.
* @throws \InvalidArgumentException
*/
public function __construct($prefixes, $targetClassName)
{
parent::__construct($targetClassName);
if (is_string($prefixes)) {
$this->prefixes = array($prefixes);
} elseif (is_array($prefixes)) {
$this->prefixes = $prefixes;
} else {
throw new InvalidArgumentException("$prefixes must be a string or array of string");
}
}
// ------------------------------------------------------------------------
/* HandlerInterface */
@ -32,12 +56,32 @@ class PrefixRoute extends StaticRoute
public function getResponse(RequestInterface $request, array $args = null)
{
$requestPath = $request->getPath();
foreach ($this->paths as $path) {
if (substr($requestPath, 0, strlen($path)) === $path) {
foreach ($this->prefixes as $prefix) {
if (strrpos($requestPath, $prefix, -strlen($requestPath)) !== false) {
$target = $this->getTarget();
return $target->getResponse($request, $args);
}
}
return null;
}
/**
* Returns the path prefixes this maps to a target handler.
*
* @return array Array of path prefixes.
*/
public function getPrefixes()
{
return $this->prefixes;
}
/**
* Returns the target class this maps to.
*
* @return string Fully qualified name for a HandlerInterface
*/
public function getHandler()
{
return $this->getTarget();
}
}

View File

@ -8,6 +8,7 @@ use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
use pjdietz\WellRESTed\Response;
use pjdietz\WellRESTed\Router;
use pjdietz\WellRESTed\Routes\PrefixRoute;
use pjdietz\WellRESTed\Routes\StaticRoute;
use pjdietz\WellRESTed\Routes\TemplateRoute;
@ -48,6 +49,81 @@ class RouterTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(200, $resp->getStatusCode());
}
public function testAddStaticRoute()
{
$path = "/cats/";
$mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface');
$mockRequest->expects($this->any())
->method('getPath')
->will($this->returnValue($path));
$router = new Router();
$router->setStaticRoute($path, __NAMESPACE__ . '\\RouterTestHandler');
$resp = $router->getResponse($mockRequest);
$this->assertNotNull($resp);
}
public function testAddPrefixRoute()
{
$path = "/cats/";
$mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface');
$mockRequest->expects($this->any())
->method('getPath')
->will($this->returnValue($path));
$router = new Router();
$router->setPrefixRoute($path, __NAMESPACE__ . '\\RouterTestHandler');
$resp = $router->getResponse($mockRequest);
$this->assertNotNull($resp);
}
public function testMatchStaticRouteBeforePrefixRoute()
{
$mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface');
$mockRequest->expects($this->any())
->method('getPath')
->will($this->returnValue("/amimals/cats/molly"));
$router = new Router();
$router->addRoute(new PrefixRoute("/amimals/", __NAMESPACE__ . '\\NotFoundHandler'));
$router->addRoute(new PrefixRoute("/amimals/cats/", __NAMESPACE__ . '\\NotFoundHandler'));
$router->addRoute(new StaticRoute("/amimals/cats/molly", __NAMESPACE__ . '\\RouterTestHandler'));
$resp = $router->getResponse($mockRequest);
$this->assertEquals(200, $resp->getStatusCode());
}
public function testMatchBestPrefixRoute()
{
$mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface');
$mockRequest->expects($this->any())
->method('getPath')
->will($this->returnValue("/amimals/cats/molly"));
$router = new Router();
$router->addRoute(new PrefixRoute("/amimals/", __NAMESPACE__ . '\\NotFoundHandler'));
$router->addRoute(new PrefixRoute("/amimals/dogs/", __NAMESPACE__ . '\\NotFoundHandler'));
$router->addRoute(new PrefixRoute("/amimals/cats/", __NAMESPACE__ . '\\RouterTestHandler'));
$resp = $router->getResponse($mockRequest);
$this->assertEquals(200, $resp->getStatusCode());
}
public function testMatchBestPrefixRouteBeforeTemplateRoute()
{
$mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface');
$mockRequest->expects($this->any())
->method('getPath')
->will($this->returnValue("/amimals/cats/molly"));
$router = new Router();
$router->addRoute(new PrefixRoute("/amimals/", __NAMESPACE__ . '\\NotFoundHandler'));
$router->addRoute(new PrefixRoute("/amimals/cats/", __NAMESPACE__ . '\\RouterTestHandler'));
$router->addRoute(new TemplateRoute("/amimals/cats/*", __NAMESPACE__ . '\\NotFoundHandler'));
$resp = $router->getResponse($mockRequest);
$this->assertEquals(200, $resp->getStatusCode());
}
public function testRespondWithDefaultErrorForException()
{
$mockRequest = $this->getMock('\pjdietz\WellRESTed\Interfaces\RequestInterface');
@ -337,7 +413,6 @@ class InjectionErrorHandler implements HandlerInterface
}
}
class InjectionHandler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)