Update TemplateRoute

This commit is contained in:
PJ Dietz 2015-05-08 00:25:15 -04:00
parent 8f4165cdb6
commit 09ea17d349
2 changed files with 61 additions and 90 deletions

View File

@ -5,60 +5,27 @@ namespace WellRESTed\Routing\Route;
class TemplateRoute extends RegexRoute class TemplateRoute extends RegexRoute
{ {
/** /**
* Regular expression matching URL friendly characters (i.e., letters, * Regular expression matching 1 or more unreserved characters.
* digits, hyphen and underscore) * ALPHA / DIGIT / "-" / "." / "_" / "~"
*/ */
const RE_SLUG = '[0-9a-zA-Z\-_]+'; const RE_UNRESERVED = '[0-9a-zA-Z\-._\~]+';
/** Regular expression matching digitis */
const RE_NUM = '[0-9]+';
/** Regular expression matching letters */
const RE_ALPHA = '[a-zA-Z]+';
/** Regular expression matching letters and digits */
const RE_ALPHANUM = '[0-9a-zA-Z]+';
/** Regular expression matching a URI template variable (e.g., {id}) */ /** Regular expression matching a URI template variable (e.g., {id}) */
const URI_TEMPLATE_EXPRESSION_RE = '/{([[a-zA-Z][a-zA-Z0-_]*)}/'; const URI_TEMPLATE_EXPRESSION_RE = '/{([[a-zA-Z][a-zA-Z0-_]*)}/';
/** public function __construct($target, $methodMap)
* Create a new route that matches a URI template to a handler. {
* $pattern = $this->buildPattern($target);
* Optionally provide patterns for the variables in the template. parent::__construct($pattern, $methodMap);
*
* @param string $template URI template the path must match
* @param mixed $target Handler to dispatch
* @param string|array $variablePattern Regular expression for variables
*
* @see BaseRoute for details about $target
*/
public function __construct(
$template,
$middleware,
$variablePattern = self::RE_SLUG
) {
$pattern = $this->buildPattern($template, $variablePattern);
parent::__construct($pattern, $middleware);
} }
/** /**
* Translate the URI template into a regular expression. * Translate the URI template into a regular expression.
* *
* @param string $template URI template the path must match * @param string $template URI template the path must match
* @param string|array $variablePattern Regular expression for variables
* @return string * @return string
*/ */
private function buildPattern($template, $variablePattern) private function buildPattern($template)
{ {
$defaultPattern = self::RE_SLUG;
$variablePatterns = [];
if (is_string($variablePattern)) {
$defaultPattern = $variablePattern;
} elseif (is_array($variablePattern)) {
$variablePatterns = $variablePattern;
if (isset($variablePatterns["*"])) {
$defaultPattern = $variablePatterns["*"];
}
}
// Convert the template into the pattern // Convert the template into the pattern
$pattern = $template; $pattern = $template;
@ -75,13 +42,10 @@ class TemplateRoute extends RegexRoute
$pattern = "~^{$pattern}$~"; $pattern = "~^{$pattern}$~";
// Replace all template variables with matching subpatterns. // Replace all template variables with matching subpatterns.
$callback = function ($matches) use ($variablePatterns, $defaultPattern) { $callback = function ($matches) {
$key = $matches[1]; $key = $matches[1];
if (isset($variablePatterns[$key])) { // TODO Check for reserved characters, etc.
$pattern = $variablePatterns[$key]; $pattern = self::RE_UNRESERVED;
} else {
$pattern = $defaultPattern;
}
return "(?<{$key}>{$pattern})"; return "(?<{$key}>{$pattern})";
}; };
$pattern = preg_replace_callback(self::URI_TEMPLATE_EXPRESSION_RE, $callback, $pattern); $pattern = preg_replace_callback(self::URI_TEMPLATE_EXPRESSION_RE, $callback, $pattern);

View File

@ -3,6 +3,7 @@
namespace WellRESTed\Test\Unit\Routing\Route; namespace WellRESTed\Test\Unit\Routing\Route;
use Prophecy\Argument; use Prophecy\Argument;
use WellRESTed\Routing\Route\RouteInterface;
use WellRESTed\Routing\Route\TemplateRoute; use WellRESTed\Routing\Route\TemplateRoute;
/** /**
@ -12,42 +13,54 @@ use WellRESTed\Routing\Route\TemplateRoute;
*/ */
class TemplateRouteTest extends \PHPUnit_Framework_TestCase class TemplateRouteTest extends \PHPUnit_Framework_TestCase
{ {
private $request; private $methodMap;
private $response;
private $middleware;
public function setUp() public function setUp()
{ {
$this->request = $this->prophesize("\\Psr\\Http\\Message\\ServerRequestInterface"); $this->methodMap = $this->prophesize('WellRESTed\Routing\MethodMapInterface');
$this->response = $this->prophesize("\\Psr\\Http\\Message\\ResponseInterface"); }
$this->middleware = $this->prophesize("\\WellRESTed\\Routing\\MiddlewareInterface");
/**
* @coversNothing
*/
public function testReturnsPatternType()
{
$route = new TemplateRoute("/", $this->methodMap->reveal());
$this->assertSame(RouteInterface::TYPE_PATTERN, $route->getType());
} }
/** /**
* @dataProvider matchingTemplateProvider * @dataProvider matchingTemplateProvider
*/ */
public function testMatchesTemplate($template, $vars, $path) public function testMatchesTemplate($template, $requestTarget)
{ {
$route = new TemplateRoute($template, $this->middleware->reveal(), $vars); $route = new TemplateRoute($template, $this->methodMap->reveal());
$this->assertTrue($route->matchesRequestTarget($path)); $this->assertTrue($route->matchesRequestTarget($requestTarget));
} }
/** /**
* @dataProvider matchingTemplateProvider * @dataProvider matchingRouteProvider
*/ */
public function testExtractsCaptures($template, $vars, $path, $expectedCaptures) public function testProvidesCapturesAsRequestAttributes($template, $path, $expectedCaptures)
{ {
$route = new TemplateRoute($template, $this->middleware->reveal(), $vars); $request = $this->prophesize('Psr\Http\Message\ServerRequestInterface');
$route->matchesRequestTarget($path, $captures); $request->withAttribute(Argument::cetera())->willReturn($request->reveal());
$this->assertEquals(0, count(array_diff_assoc($expectedCaptures, $captures))); $response = $this->prophesize('Psr\Http\Message\ResponseInterface');
$responseReveal = $response->reveal();
$route = new TemplateRoute($template, $this->methodMap->reveal());
$route->matchesRequestTarget($path);
$route->dispatch($request->reveal(), $responseReveal);
$request->withAttribute("path", $expectedCaptures)->shouldHaveBeenCalled();
} }
public function matchingTemplateProvider() public function matchingTemplateProvider()
{ {
return [ return [
["/cat/{id}", TemplateRoute::RE_NUM, "/cat/12", ["id" => "12"]], ["/cat/{id}", "/cat/12", ["id" => "12"]],
["/unreserved/{id}", "/unreserved/az0-._~", ["id" => "az0-._~"]],
["/cat/{catId}/{dogId}", ["/cat/{catId}/{dogId}",
TemplateRoute::RE_SLUG,
"/cat/molly/bear", "/cat/molly/bear",
[ [
"catId" => "molly", "catId" => "molly",
@ -56,28 +69,22 @@ class TemplateRouteTest extends \PHPUnit_Framework_TestCase
], ],
[ [
"/cat/{catId}/{dogId}", "/cat/{catId}/{dogId}",
[
"catId" => TemplateRoute::RE_SLUG,
"dogId" => TemplateRoute::RE_SLUG
],
"/cat/molly/bear", "/cat/molly/bear",
[ [
"catId" => "molly", "catId" => "molly",
"dogId" => "bear" "dogId" => "bear"
] ]
], ],
["/cat/{id}/*", null, "/cat/12/molly", ["id" => "12"]], ["/cat/{id}/*", "/cat/12/molly", ["id" => "12"]],
[ [
"/cat/{id}-{width}x{height}.jpg", "/cat/{id}-{width}x{height}.jpg",
TemplateRoute::RE_NUM,
"/cat/17-200x100.jpg", "/cat/17-200x100.jpg",
[ [
"id" => "17", "id" => "17",
"width" => "200", "width" => "200",
"height" => "100" "height" => "100"
] ]
], ]
["/cat/{path}", ".*", "/cat/this/section/has/slashes", ["path" => "this/section/has/slashes"]]
]; ];
} }
@ -86,9 +93,18 @@ class TemplateRouteTest extends \PHPUnit_Framework_TestCase
*/ */
public function testMatchesAllowedVariablesNames($template, $path, $expectedCaptures) public function testMatchesAllowedVariablesNames($template, $path, $expectedCaptures)
{ {
$route = new TemplateRoute($template, $this->middleware->reveal()); $request = $this->prophesize('Psr\Http\Message\ServerRequestInterface');
$route->matchesRequestTarget($path, $captures); $request->withAttribute(Argument::cetera())->willReturn($request->reveal());
$this->assertEquals(0, count(array_diff_assoc($expectedCaptures, $captures))); $response = $this->prophesize('Psr\Http\Message\ResponseInterface');
$responseReveal = $response->reveal();
$route = new TemplateRoute($template, $this->methodMap->reveal());
$route->matchesRequestTarget($path);
$route->dispatch($request->reveal(), $responseReveal);
$request->withAttribute("path", Argument::that(function ($path) use ($expectedCaptures) {
return array_intersect_assoc($path, $expectedCaptures) == $expectedCaptures;
}))->shouldHaveBeenCalled();
} }
public function allowedVariableNamesProvider() public function allowedVariableNamesProvider()
@ -106,8 +122,8 @@ class TemplateRouteTest extends \PHPUnit_Framework_TestCase
*/ */
public function testFailsToMatchIllegalVariablesNames($template, $path) public function testFailsToMatchIllegalVariablesNames($template, $path)
{ {
$route = new TemplateRoute($template, $this->middleware->reveal()); $route = new TemplateRoute($template, $this->methodMap->reveal());
$this->assertFalse($route->matchesRequestTarget($path, $captures)); $this->assertFalse($route->matchesRequestTarget($path));
} }
public function illegalVariableNamesProvider() public function illegalVariableNamesProvider()
@ -124,26 +140,17 @@ class TemplateRouteTest extends \PHPUnit_Framework_TestCase
/** /**
* @dataProvider nonmatchingTemplateProvider * @dataProvider nonmatchingTemplateProvider
*/ */
public function testFailsToMatchNonmatchingTemplate($template, $vars, $path) public function testFailsToMatchNonmatchingTemplate($template, $path)
{ {
$route = new TemplateRoute($template, $this->middleware->reveal(), $vars); $route = new TemplateRoute($template, $this->methodMap->reveal());
$this->assertFalse($route->matchesRequestTarget($path, $captures)); $this->assertFalse($route->matchesRequestTarget($path));
} }
public function nonmatchingTemplateProvider() public function nonmatchingTemplateProvider()
{ {
return [ return [
["/cat/{id}", TemplateRoute::RE_NUM, "/cat/molly"], ["/cat/{id}", "/cat/molly/the/cat"],
["/cat/{catId}/{dogId}", TemplateRoute::RE_ALPHA, "/cat/12/13"], ["/cat/{catId}/{dogId}", "/dog/12/13"]
[
"/cat/{catId}/{dogId}",
[
"*" => TemplateRoute::RE_NUM,
"catId" => TemplateRoute::RE_ALPHA,
"dogId" => TemplateRoute::RE_ALPHA
],
"/cat/12/13"
]
]; ];
} }
} }