diff --git a/src/Routing/Router.php b/src/Routing/Router.php index 36b8f09..01aa911 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -13,7 +13,7 @@ use WellRESTed\Routing\Route\RouteInterface; class Router implements RouterInterface { /** @var string Key to ServerRequestInterface attribute for matched path variables */ - public $pathVariablesAttributeKey = "pathVariables"; + private $pathVariablesAttributeName; /** @var DispatcherInterface */ private $dispatcher; /** @var RouteFactoryInterface */ @@ -27,12 +27,25 @@ class Router implements RouterInterface /** @var RouteInterface[] Hash array mapping path prefixes to routes */ private $patternRoutes; - public function __construct(DispatcherInterface $dispatcher = null) + /** + * Create a new Router. + * + * When the router matches a route with path variables, it will add each + * variable as an attribute on the ServerRequestInterface by default. + * + * When $pathVariablesAttributeName is set, the router will set one + * attribute with the passed name to an array containing all of the path + * variables. + * + * @param DispatcherInterface $dispatcher Instance to use for dispatching + * middleware. + * @param string $pathVariablesAttributeName Optionally provide all path + * variables as an array stored with this attribute name + */ + public function __construct(DispatcherInterface $dispatcher = null, $pathVariablesAttributeName = null) { - if ($dispatcher === null) { - $dispatcher = new Dispatcher(); - } $this->dispatcher = $dispatcher; + $this->pathVariablesAttributeName = $pathVariablesAttributeName; $this->factory = $this->getRouteFactory($this->dispatcher); $this->routes = []; $this->staticRoutes = []; @@ -40,6 +53,41 @@ class Router implements RouterInterface $this->patternRoutes = []; } + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next) + { + // Use only the path for routing. + $requestTarget = parse_url($request->getRequestTarget(), PHP_URL_PATH); + + $route = $this->getStaticRoute($requestTarget); + if ($route) { + return $route($request, $response, $next); + } + + $route = $this->getPrefixRoute($requestTarget); + if ($route) { + return $route($request, $response, $next); + } + + // Try each of the routes. + foreach ($this->patternRoutes as $route) { + if ($route->matchesRequestTarget($requestTarget)) { + $pathVariables = $route->getPathVariables(); + if ($this->pathVariablesAttributeName) { + $request = $request->withAttribute($this->pathVariablesAttributeName, $pathVariables); + } else { + foreach ($pathVariables as $name => $value) { + $request = $request->withAttribute($name, $value); + } + } + return $route($request, $response, $next); + } + } + + // If no route exists, set the status code of the response to 404 and + // return the response without propagating. + return $response->withStatus(404); + } + /** * Register middleware with the router for a given path and method. * @@ -75,35 +123,6 @@ class Router implements RouterInterface return $this; } - public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next) - { - // Use only the path for routing. - $requestTarget = parse_url($request->getRequestTarget(), PHP_URL_PATH); - - $route = $this->getStaticRoute($requestTarget); - if ($route) { - return $route($request, $response, $next); - } - - $route = $this->getPrefixRoute($requestTarget); - if ($route) { - return $route($request, $response, $next); - } - - // Try each of the routes. - foreach ($this->patternRoutes as $route) { - if ($route->matchesRequestTarget($requestTarget)) { - $pathVariables = $route->getPathVariables(); - $request = $request->withAttribute($this->pathVariablesAttributeKey, $pathVariables); - return $route($request, $response, $next); - } - } - - // If no route exists, set the status code of the response to 404 and - // return the response without propagating. - return $response->withStatus(404); - } - /** * @param DispatcherInterface * @return RouteFactoryInterface diff --git a/test/tests/unit/Routing/RouterTest.php b/test/tests/unit/Routing/RouterTest.php index 1e27718..ec53416 100644 --- a/test/tests/unit/Routing/RouterTest.php +++ b/test/tests/unit/Routing/RouterTest.php @@ -75,20 +75,8 @@ class RouterTest extends \PHPUnit_Framework_TestCase */ public function testCreatesInstance() { - $routeMap = new Router($this->dispatcher->reveal()); - $this->assertNotNull($routeMap); - } - - /** - * @covers ::__construct - * @covers ::getRouteFactory - * @uses WellRESTed\Routing\Route\RouteFactory - * @uses WellRESTed\Dispatching\Dispatcher - */ - public function testCreatesInstanceWithDispatcherByDefault() - { - $routeMap = new Router(); - $this->assertNotNull($routeMap); + $router = new Router($this->dispatcher->reveal()); + $this->assertNotNull($router); } // ------------------------------------------------------------------------ @@ -339,30 +327,6 @@ class RouterTest extends \PHPUnit_Framework_TestCase $patternRoute2->matchesRequestTarget(Argument::any())->shouldNotHaveBeenCalled(); } - /** - * @covers ::__invoke - * @group current - */ - public function testSetPathVariablesAttributeBeforeDispatchingPatternRoute() - { - $target = "/"; - $variables = [ - "id" => "1024", - "name" => "Molly" - ]; - - $this->request->getRequestTarget()->willReturn($target); - $this->route->getTarget()->willReturn($target); - $this->route->getType()->willReturn(RouteInterface::TYPE_PATTERN); - $this->route->matchesRequestTarget(Argument::cetera())->willReturn(true); - $this->route->getPathVariables()->willReturn($variables); - - $this->router->register("GET", $target, "middleware"); - $this->router->__invoke($this->request->reveal(), $this->response->reveal(), $this->next); - - $this->request->withAttribute("pathVariables", $variables)->shouldHaveBeenCalled(); - } - /** * @covers ::__invoke * @covers ::registerRouteForTarget @@ -382,6 +346,70 @@ class RouterTest extends \PHPUnit_Framework_TestCase $this->route->matchesRequestTarget("/my/path")->shouldHaveBeenCalled(); } + // ------------------------------------------------------------------------ + // Path Variables + + /** + * @covers ::__invoke + * @dataProvider pathVariableProvider + */ + public function testSetPathVariablesAttributeIndividually($name, $value) + { + $attributeName = "pathVariables"; + + $target = "/"; + $variables = [ + "id" => "1024", + "name" => "Molly" + ]; + + $this->request->getRequestTarget()->willReturn($target); + $this->route->getTarget()->willReturn($target); + $this->route->getType()->willReturn(RouteInterface::TYPE_PATTERN); + $this->route->matchesRequestTarget(Argument::cetera())->willReturn(true); + $this->route->getPathVariables()->willReturn($variables); + + $this->router->__construct($this->dispatcher->reveal()); + $this->router->register("GET", $target, "middleware"); + $this->router->__invoke($this->request->reveal(), $this->response->reveal(), $this->next); + + $this->request->withAttribute($name, $value)->shouldHaveBeenCalled(); + } + + public function pathVariableProvider() + { + return [ + ["id", "1024"], + ["name", "Molly"] + ]; + } + + /** + * @covers ::__invoke + */ + public function testSetPathVariablesAttributeAsArray() + { + $attributeName = "pathVariables"; + + $target = "/"; + $variables = [ + "id" => "1024", + "name" => "Molly" + ]; + + $this->request->getRequestTarget()->willReturn($target); + $this->route->getTarget()->willReturn($target); + $this->route->getType()->willReturn(RouteInterface::TYPE_PATTERN); + $this->route->matchesRequestTarget(Argument::cetera())->willReturn(true); + $this->route->getPathVariables()->willReturn($variables); + + $this->router->__construct($this->dispatcher->reveal(), $attributeName); + $this->router->register("GET", $target, "middleware"); + $this->router->__invoke($this->request->reveal(), $this->response->reveal(), $this->next); + + $this->request->withAttribute("pathVariables", $variables)->shouldHaveBeenCalled(); + } + // ------------------------------------------------------------------------ // No Matching Routes