Browse Source

Start integrating Router + new route collections.

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.
mark_story 11 years ago
parent
commit
f03b40e4db
2 changed files with 87 additions and 71 deletions
  1. 87 53
      src/Routing/Router.php
  2. 0 18
      tests/TestCase/Routing/RouterTest.php

+ 87 - 53
src/Routing/Router.php

@@ -125,6 +125,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
  *
  * @var array
@@ -337,23 +353,12 @@ class Router {
 	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);
+		});
 	}
 
 /**
@@ -390,9 +395,6 @@ class Router {
  */
 	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);
 	}
 
@@ -503,16 +505,24 @@ class Router {
  *
  * @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.');
 	}
 
 /**
@@ -565,7 +575,22 @@ class Router {
  */
 	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()
+		];
 	}
 
 /**
@@ -579,7 +604,7 @@ class Router {
 		$removed = array_pop(static::$_requests);
 		$last = end(static::$_requests);
 		if ($last) {
-			static::$_routes->setContext($last);
+			static::_setContext($last);
 			reset(static::$_requests);
 		}
 		return $removed;
@@ -607,8 +632,6 @@ class Router {
 	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) {
@@ -616,19 +639,6 @@ class Router {
 				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);
 	}
 
 /**
@@ -809,28 +819,18 @@ class Router {
 				'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) {
@@ -849,6 +849,41 @@ class Router {
 	}
 
 /**
+ * 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,
  * the currently configured value is returned.
@@ -972,7 +1007,6 @@ class Router {
 		if ($merge) {
 			$extensions = array_merge(static::$_validExtensions, $extensions);
 		}
-		static::$_routes->parseExtensions($extensions);
 		return static::$_validExtensions = $extensions;
 	}
 
@@ -1098,7 +1132,7 @@ class Router {
 		} else {
 			static::$_pathScopes[$path]->merge($collection);
 		}
-		return $collection;
+		static::$_named += $collection->named();
 	}
 
 /**

+ 0 - 18
tests/TestCase/Routing/RouterTest.php

@@ -2839,24 +2839,6 @@ class RouterTest extends TestCase {
 	}
 
 /**
- * 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
  *
  * @return void