Skip to content

Commit

Permalink
Start integrating Router + new route collections.
Browse files Browse the repository at this point in the history
This also removes Router::promote(). I feel this was infrequently used,
and can be better accomplished by correctly defining your routes.
promote() becomes very difficult when we start segmenting/sharding the
routes as is done with scoped collections.
  • Loading branch information
markstory committed Jun 26, 2014
1 parent bcc20c4 commit f03b40e
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 71 deletions.
140 changes: 87 additions & 53 deletions src/Routing/Router.php
Expand Up @@ -124,6 +124,22 @@ class Router {
*/
protected static $_pathScopes = [];

/**
* A hash of request context data.
*
* @var array
*/
protected static $_requestContext = [];

/**
* A hash of named routes. Indexed
* as an optimization so reverse routing does not
* have to traverse all the route collections.
*
* @var array
*/
protected static $_named = [];

/**
* Named expressions
*
Expand Down Expand Up @@ -337,23 +353,12 @@ public static function resourceMap($resourceMap = null) {
public static function connect($route, $defaults = [], $options = []) {
static::$initialized = true;

$defaults += ['plugin' => null];
if (empty($options['action'])) {
$defaults += array('action' => 'index');
}
if (empty($options['_ext'])) {
$options['_ext'] = static::$_validExtensions;
if (empty($options['routeClass'])) {
$options['routeClass'] = static::$_routeClass;
}
$routeClass = static::$_routeClass;
if (isset($options['routeClass'])) {
$routeClass = App::className($options['routeClass'], 'Routing/Route');
$routeClass = static::_validateRouteClass($routeClass);
unset($options['routeClass']);
}
if ($routeClass === 'Cake\Routing\Route\RedirectRoute' && isset($defaults['redirect'])) {
$defaults = $defaults['redirect'];
}
static::$_routes->add(new $routeClass($route, $defaults, $options));
Router::scope('/', function($routes) use ($route, $defaults, $options) {
$routes->connect($route, $defaults, $options);
});
}

/**
Expand Down Expand Up @@ -390,9 +395,6 @@ public static function connect($route, $defaults = [], $options = []) {
*/
public static function redirect($route, $url, $options = []) {
$options['routeClass'] = 'Cake\Routing\Route\RedirectRoute';
if (is_string($url)) {
$url = array('redirect' => $url);
}
return static::connect($route, $url, $options);
}

Expand Down Expand Up @@ -503,16 +505,24 @@ public static function prefixes() {
*
* @param string $url URL to be parsed
* @return array Parsed elements from URL
* @throws \Cake\Error\Exception When a route cannot be handled
*/
public static function parse($url) {
if (!static::$initialized) {
static::_loadRoutes();
}

if (strlen($url) && strpos($url, '/') !== 0) {
$url = '/' . $url;
}
return static::$_routes->parse($url);

krsort(static::$_pathScopes);
foreach (static::$_pathScopes as $path => $collection) {
if (strpos($url, $path) === 0) {
return $collection->parse($url);
}
}
// TODO improve this with a custom exception.
throw new Error\Exception('No routes match the given URL.');
}

/**
Expand Down Expand Up @@ -565,7 +575,22 @@ public static function setRequestInfo($request) {
*/
public static function pushRequest(Request $request) {
static::$_requests[] = $request;
static::$_routes->setContext($request);
static::_setContext($request);
}

/**
* Store the request context for a given request.
*
* @param \Cake\Network\Request $request The request instance.
* @return void
*/
protected static function _setContext($request) {
static::$_requestContext = [
'_base' => $request->base,
'_port' => $request->port(),
'_scheme' => $request->scheme(),
'_host' => $request->host()
];
}

/**
Expand All @@ -579,7 +604,7 @@ public static function popRequest() {
$removed = array_pop(static::$_requests);
$last = end(static::$_requests);
if ($last) {
static::$_routes->setContext($last);
static::_setContext($last);
reset(static::$_requests);
}
return $removed;
Expand Down Expand Up @@ -607,28 +632,13 @@ public static function getRequest($current = false) {
public static function reload() {
if (empty(static::$_initialState)) {
static::$_initialState = get_class_vars(get_called_class());
static::_setPrefixes();
static::$_routes = new RouteCollection();
return;
}
foreach (static::$_initialState as $key => $val) {
if ($key != '_initialState') {
static::${$key} = $val;
}
}
static::_setPrefixes();
static::$_routes = new RouteCollection();
}

/**
* Promote a route (by default, the last one added) to the beginning of the list
*
* @param int $which A zero-based array index representing the route to move. For example,
* if 3 routes have been added, the last route would be 2.
* @return bool Returns false if no route exists at the position specified by $which.
*/
public static function promote($which = null) {
return static::$_routes->promote($which);
}

/**
Expand Down Expand Up @@ -809,28 +819,18 @@ public static function url($url = null, $options = []) {
'controller' => $params['controller'],
'action' => 'index',
'_ext' => $params['_ext']

);
$url = static::_applyUrlFilters($url);
$output = static::$_routes->match($url);
$output = static::_match($url);
} elseif (
$urlType === 'string' &&
!$hasLeadingSlash &&
!$plainString
) {
// named route.
$route = static::$_routes->get($url);
if (!$route) {
throw new Error\Exception(sprintf(
'No route matching the name "%s" was found.',
$url
));
}
$url = $options +
$route->defaults +
array('_name' => $url);
$url = $options + ['_name' => $url];
$url = static::_applyUrlFilters($url);
$output = static::$_routes->match($url);
$output = static::_match($url);
} else {
// String urls.
if ($plainString) {
Expand All @@ -848,6 +848,41 @@ public static function url($url = null, $options = []) {
return $output . $frag;
}

/**
* Find a Route that matches the given URL data.
*
* @param string|array The URL to match.
* @return string The generated URL
* @throws \Cake\Error\Exception When a matching URL cannot be found.
*/
protected static function _match($url) {
// Named routes support hack.
if (isset($url['_name'])) {
$route = false;
if (isset(static::$_named[$url['_name']])) {
$route = static::$_named[$url['_name']];
}
if ($route) {
unset($url['_name']);
return $route->match($url + $route->defaults, static::$_requestContext);
}
}

// No quick win, iterate and hope for the best.
foreach (static::$_pathScopes as $key => $collection) {
$match = $collection->match($url, static::$_requestContext);
if ($match) {
return $match;
}
}

// TODO improve with custom exception
throw new Error\Exception(sprintf(
'Unable to find a matching route for %s',
var_export($url, true)
));
}

/**
* Sets the full base URL that will be used as a prefix for generating
* fully qualified URLs for this application. If not parameters are passed,
Expand Down Expand Up @@ -972,7 +1007,6 @@ public static function parseExtensions($extensions = null, $merge = true) {
if ($merge) {
$extensions = array_merge(static::$_validExtensions, $extensions);
}
static::$_routes->parseExtensions($extensions);
return static::$_validExtensions = $extensions;
}

Expand Down Expand Up @@ -1098,7 +1132,7 @@ public static function scope($path, $params = [], $callback = null) {
} else {
static::$_pathScopes[$path]->merge($collection);
}
return $collection;
static::$_named += $collection->named();
}

/**
Expand Down
18 changes: 0 additions & 18 deletions tests/TestCase/Routing/RouterTest.php
Expand Up @@ -2838,24 +2838,6 @@ public function testParseNamedParameters() {
$this->assertEquals($expected, $request->params);
}

/**
* Test promote()
*
* @return void
*/
public function testPromote() {
Router::connect('/:controller/:action/*');
Router::connect('/:lang/:controller/:action/*');

$result = Router::url(['controller' => 'posts', 'action' => 'index', 'lang' => 'en']);
$this->assertEquals('/posts/index?lang=en', $result, 'First route should match');

Router::promote();

$result = Router::url(['controller' => 'posts', 'action' => 'index', 'lang' => 'en']);
$this->assertEquals('/en/posts/index', $result, 'promote() should move 2nd route ahead.');
}

/**
* Test the scope() method
*
Expand Down

0 comments on commit f03b40e

Please sign in to comment.