Skip to content
Permalink
Browse files

Add ability to set Router::scopes() and use prefix()

Fix problems and add tests.
  • Loading branch information...
markstory committed Jun 24, 2014
1 parent b7f87e9 commit 67d9b428c4fb8d924b6548bb045d5fc145b19296
Showing with 114 additions and 20 deletions.
  1. +63 −0 src/Routing/Router.php
  2. +21 −20 src/Routing/ScopedRouteCollection.php
  3. +30 −0 tests/TestCase/Routing/ScopedRouteCollectionTest.php
@@ -19,6 +19,7 @@
use Cake\Error;
use Cake\Network\Request;
use Cake\Routing\RouteCollection;
use Cake\Routing\ScopedRouteCollection;
use Cake\Routing\Route\Route;
use Cake\Utility\Inflector;
@@ -116,6 +117,13 @@ class Router {
*/
const UUID = '[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}';
/**
* A hash of ScopedRouteCollection objects indexed by path.
*
* @var array
*/
protected static $_pathScopes = [];
/**
* Named expressions
*
@@ -1035,6 +1043,61 @@ public static function parseNamedParams(Request $request, $options = []) {
return $request;
}
/**
* Create a routing scope.
*
* Routing scopes allow you to keep your routes DRY and avoid repeating
* common path prefixes, and or parameter sets.
*
* Scoped collections will be indexed by path for faster route parsing. If you
* re-open or re-use a scope the connected routes will be merged with the
* existing ones.
*
* ### Example
*
* {{{
* Router::scope('/blog', ['plugin' => 'Blog'], function($routes) {
* $routes->connect('/', ['controller' => 'Articles']);
* });
* }}}
*
* The above would result in a `/blog/` route being created, with both the
* plugin & controller default parameters set.
*
* You can use Router::plugin() and Router::prefix() as shortcuts to creating
* specific kinds of scopes.
*
* Routing scopes will inherit the globally set extensions configured with
* Router::parseExtensions(). You can also set valid extensions using
* `$routes->extensions()` in your closure.
*
* @param string $path The path prefix for the scope. This path will be prepended
* to all routes connected in the scoped collection.
* @param array $params An array of routing defaults to add to each connected route.
* If you have no parameters, this argument can be a callable.
* @param callable $callback The callback to invoke with the scoped collection.
* @return void
*/
public static function scope($path, $params = [], $callback = null) {
if ($callback === null) {
$callback = $params;
$params = [];
}
if (!is_callable($callback)) {
$msg = 'Need a callable function/object to connect routes.';
throw Error\Exception($msg);
}
$collection = new ScopedRouteCollection($path, $params, static::$_validExtensions);
$callback($collection);
if (empty(static::$_pathScopes[$path])) {
static::$_pathScopes[$path] = $collection;
} else {
static::$_pathScopes[$path]->merge($collection);
}
}
/**
* Loads route configuration
*
@@ -427,30 +427,31 @@ public function redirect($route, $url, $options = []) {
}
/**
* Add prefixed routes.
*
* This method creates a new scoped route collection that includes
* relevant prefix information.
*
* The path parameter is used to generate the routing parameter name.
* For example a path of `admin` would result in `'prefix' => 'admin'` being
* applied to all connected routes.
*
* You can re-open a prefix as many times as necessary, as well as nest prefixes.
* Nested prefixes will result in prefix values like `admin/api` which translates
* to the `Controller\Admin\Api\` namespace.
*
* @param string $name The prefix name to use.
* @param callable $callback The callback to invoke that builds the prefixed routes.
* @return void
*/
* Add prefixed routes.
*
* This method creates a new scoped route collection that includes
* relevant prefix information.
*
* The path parameter is used to generate the routing parameter name.
* For example a path of `admin` would result in `'prefix' => 'admin'` being
* applied to all connected routes.
*
* You can re-open a prefix as many times as necessary, as well as nest prefixes.
* Nested prefixes will result in prefix values like `admin/api` which translates
* to the `Controller\Admin\Api\` namespace.
*
* @param string $name The prefix name to use.
* @param callable $callback The callback to invoke that builds the prefixed routes.
* @return void
*/
public function prefix($name, callable $callback) {
$name = Inflector::underscore($name);
$path = $this->_path . '/' . $name;
if (isset($this->_params['prefix'])) {
$name = $this->_params['prefix'] . '/' . $name;
}
$params = ['prefix' => $name];
$path = '/' . Inflector::underscore($name);
return Router::scope($path, $params, $callback);
$params = ['prefix' => $name] + $this->_params;
Router::scope($path, $params, $callback);
}
/**
@@ -168,4 +168,34 @@ public function testRedirect() {
$this->assertEquals('/forums', $route->redirect[0]);
}
/**
* Test creating sub-scopes with prefix()
*
* @return void
*/
public function testPrefix() {
$routes = new ScopedRouteCollection('/path', ['key' => 'value']);
$res = $routes->prefix('admin', function($r) {
$this->assertInstanceOf('Cake\Routing\ScopedRouteCollection', $r);
$this->assertCount(0, $r->routes());
$this->assertEquals('/path/admin', $r->path());
$this->assertEquals(['prefix' => 'admin', 'key' => 'value'], $r->params());
});
$this->assertNull($res);
}
/**
* Test creating sub-scopes with prefix()
*
* @return void
*/
public function testNestedPrefix() {
$routes = new ScopedRouteCollection('/admin', ['prefix' => 'admin']);
$res = $routes->prefix('api', function($r) {
$this->assertEquals('/admin/api', $r->path());
$this->assertEquals(['prefix' => 'admin/api'], $r->params());
});
$this->assertNull($res);
}
}

0 comments on commit 67d9b42

Please sign in to comment.
You can’t perform that action at this time.