Skip to content
Permalink
Browse files

First pass at splitting up the Router internals.

ScopedRouteCollection has a few too many jobs. It handles both the
building of routes and part of the route parsing/matching. Having the
parsing/matching spread between two classes is a bit gross and indicates
there is a missing class. Separating the building + parsing/route
storage makes a nice break that allows each class to handle one group of
tasks. This division also cleans up Router which is nice.
  • Loading branch information...
markstory committed Jul 4, 2014
1 parent 40022ab commit 78a4a81834b55c8a2d764c9bdd56e9edb08dfc17
@@ -571,4 +571,21 @@ protected function _writeUrl($params, $pass = [], $query = []) {
return $out;
}
/**
* Get the static path portion for this route.
*
* @return string
*/
public function staticPath() {
$routeKey = strpos($this->template, ':');
if ($routeKey !== false) {
return substr($this->template, 0, $routeKey);
}
$star = strpos($this->template, '*');
if ($star !== false) {
return substr($this->template, 0, $star);
}
return $this->template;
}
}
@@ -21,10 +21,12 @@
use Cake\Utility\Inflector;
/**
* Contains a collection of routes related to a specific path scope.
* Path scopes can be read with the `path()` method.
* Provides features for building routes inside scopes.
*
* Gives an easy to use way to build routes and append them
* into a route collection.
*/
class ScopedRouteCollection {
class RouteBuilder {
/**
* Regular expression for auto increment IDs
@@ -76,26 +78,20 @@ class ScopedRouteCollection {
protected $_params;
/**
* The routes connected to this collection.
*
* @var array
*/
protected $_routes = [];
/**
* The hash map of named routes that are in this collection.
* The route collection routes should be added to.
*
* @var array
* @var Cake\Routing\RouteCollection
*/
protected $_named = [];
protected $_collection;
/**
* Constructor
*
* @param string $path The path prefix the scope is for.
* @param array $params The scope's routing parameters.
*/
public function __construct($path, array $params = [], array $extensions = []) {
public function __construct($collection, $path, array $params = [], array $extensions = []) {
$this->_collection = $collection;
$this->_path = $path;
$this->_params = $params;
$this->_extensions = $extensions;
@@ -138,39 +134,6 @@ public function params() {
return $this->_params;
}
/**
* Get the explicity named routes in the collection.
*
* @return array An array of named routes indexed by their name.
*/
public function named() {
return $this->_named;
}
/**
* Get all the routes in this collection.
*
* @return array An array of routes.
*/
public function routes() {
return $this->_routes;
}
/**
* Get a route by its name.
*
* *Note* This method only works on explicitly named routes.
*
* @param string $name The name of the route to get.
* @return false|\Cake\Routing\Route The route.
*/
public function get($name) {
if (isset($this->_named[$name])) {
return $this->_named[$name];
}
return false;
}
/**
* Generate REST resource routes for the given controller(s).
*
@@ -267,7 +230,7 @@ public function resources($name, $options = [], $callback = null) {
if (is_callable($callback)) {
$idName = Inflector::singularize($urlName) . '_id';
$path = $this->_path . '/' . $urlName . '/:' . $idName;
Router::scope($path, $this->params(), $callback);
$this->scope($path, $this->params(), $callback);
}
}
@@ -352,16 +315,7 @@ public function connect($route, array $defaults = [], $options = []) {
}
$route = $this->_makeRoute($route, $defaults, $options);
if (isset($options['_name'])) {
$this->_named[$options['_name']] = $route;
}
$name = $route->getName();
if (!isset($this->_routeTable[$name])) {
$this->_routeTable[$name] = [];
}
$this->_routeTable[$name][] = $route;
$this->_routes[] = $route;
$this->_collection->add($route, $options);
}
/**
@@ -385,9 +339,6 @@ protected function _makeRoute($route, $defaults, $options) {
unset($options['routeClass']);
$route = str_replace('//', '/', $this->_path . $route);
if (!is_array($defaults)) {
debug(\Cake\Utility\Debugger::trace());
}
foreach ($this->_params as $param => $val) {
if (isset($defaults[$param]) && $defaults[$param] !== $val) {
$msg = 'You cannot define routes that conflict with the scope. ' .
@@ -477,7 +428,7 @@ public function prefix($name, callable $callback) {
$name = $this->_params['prefix'] . '/' . $name;
}
$params = ['prefix' => $name] + $this->_params;
Router::scope($path, $params, $callback);
$this->scope($path, $params, $callback);
}
/**
@@ -508,122 +459,21 @@ public function plugin($name, $options = [], $callback = null) {
$options['path'] = '/' . Inflector::underscore($name);
}
$options['path'] = $this->_path . $options['path'];
Router::scope($options['path'], $params, $callback);
}
/**
* Takes the URL string and iterates the routes until one is able to parse the route.
*
* @param string $url Url to parse.
* @return array An array of request parameters parsed from the url.
*/
public function parse($url) {
$queryParameters = null;
if (strpos($url, '?') !== false) {
list($url, $queryParameters) = explode('?', $url, 2);
parse_str($queryParameters, $queryParameters);
}
$out = [];
for ($i = 0, $len = count($this->_routes); $i < $len; $i++) {
$r = $this->_routes[$i]->parse($url);
if ($r === false) {
continue;
}
if ($queryParameters) {
$r['?'] = $queryParameters;
return $r;
}
return $r;
}
return $out;
}
/**
* Reverse route or match a $url array with the defined routes.
* Returns either the string URL generate by the route, or false on failure.
*
* @param array $url The url to match.
* @param array $context The request context to use. Contains _base, _port,
* _host, and _scheme keys.
* @return string|false Either a string on match, or false on failure.
*/
public function match($url, $context) {
foreach ($this->_getNames($url) as $name) {
if (empty($this->_routeTable[$name])) {
continue;
}
foreach ($this->_routeTable[$name] as $route) {
$match = $route->match($url, $context);
if ($match) {
return strlen($match) > 1 ? trim($match, '/') : $match;
}
}
}
return false;
$this->scope($options['path'], $params, $callback);
}
/**
* Get the set of names from the $url. Accepts both older style array urls,
* and newer style urls containing '_name'
*
* @param array $url The url to match.
* @return string The name of the url
*/
protected function _getNames($url) {
$name = false;
if (isset($url['_name'])) {
return [$url['_name']];
}
$plugin = false;
if (isset($url['plugin'])) {
$plugin = $url['plugin'];
}
$fallbacks = [
'%2$s:%3$s',
'%2$s:_action',
'_controller:%3$s',
'_controller:_action'
];
if ($plugin) {
$fallbacks = [
'%1$s.%2$s:%3$s',
'%1$s.%2$s:_action',
'%1$s._controller:%3$s',
'%1$s._controller:_action',
'_plugin.%2$s:%3$s',
'_plugin._controller:%3$s',
'_plugin._controller:_action',
'_controller:_action'
];
}
foreach ($fallbacks as $i => $template) {
$fallbacks[$i] = strtolower(sprintf($template, $plugin, $url['controller'], $url['action']));
public function scope($path, $params, $callback) {
if ($callback === null) {
$callback = $params;
$params = [];
}
if ($name) {
array_unshift($fallbacks, $name);
if (!is_callable($callback)) {
$msg = 'Need a callable function/object to connect routes.';
throw new \InvalidArgumentException($msg);
}
return $fallbacks;
}
/**
* Merge another ScopedRouteCollection with this one.
*
* Combines all the routes, from one collection into the current one.
* Used internally when scopes are duplicated.
*
* @param \Cake\Routing\ScopedRouteCollection $collection
* @return void
*/
public function merge(ScopedRouteCollection $collection) {
foreach ($collection->routes() as $route) {
$name = $route->getName();
if (!isset($this->_routeTable[$name])) {
$this->_routeTable[$name] = [];
}
$this->_routeTable[$name][] = $route;
$this->_routes[] = $route;
}
$this->_named += $collection->named();
$builder = new static($this->_collection, $path, $params, $this->_extensions);
$callback($builder);
}
/**
Oops, something went wrong.

0 comments on commit 78a4a81

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