Skip to content

Commit

Permalink
Merge pull request #3898 from markstory/3.0-route-collection
Browse files Browse the repository at this point in the history
3.0 Router cleanup
  • Loading branch information
lorenzo committed Jul 7, 2014
2 parents f612bb1 + f981c7c commit 106fee0
Show file tree
Hide file tree
Showing 14 changed files with 716 additions and 630 deletions.
11 changes: 1 addition & 10 deletions src/Controller/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -392,20 +392,11 @@ public function invokeAction() {
* @return bool
*/
protected function _isPrivateAction(\ReflectionMethod $method, Request $request) {
$privateAction = (
return (
$method->name[0] === '_' ||
!$method->isPublic() ||
!in_array($method->name, $this->methods)
);
$prefixes = Router::prefixes();

if (!$privateAction && !empty($prefixes)) {
if (empty($request->params['prefix']) && strpos($request->params['action'], '_') > 0) {
list($prefix) = explode('_', $request->params['action']);
$privateAction = in_array($prefix, $prefixes);
}
}
return $privateAction;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Routing/Filter/ControllerFactoryFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Cake\Core\App;
use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;
use Cake\Utility\Inflector;

/**
* A dispatcher filter that builds the controller to dispatch
Expand Down Expand Up @@ -64,7 +65,7 @@ protected function _getController($request, $response) {
$controller = $request->params['controller'];
}
if (!empty($request->params['prefix'])) {
$namespace .= '/' . $request->params['prefix'];
$namespace .= '/' . Inflector::camelize($request->params['prefix']);
}
$className = false;
if ($pluginPath . $controller) {
Expand Down
6 changes: 0 additions & 6 deletions src/Routing/Route/InflectedRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ public function parse($url) {
if (!empty($params['plugin'])) {
$params['plugin'] = Inflector::camelize($params['plugin']);
}
if (!empty($params['prefix'])) {
$params['prefix'] = Inflector::camelize($params['prefix']);
}
return $params;
}

Expand All @@ -64,9 +61,6 @@ public function match(array $url, array $context = array()) {
if (!empty($url['plugin'])) {
$url['plugin'] = Inflector::underscore($url['plugin']);
}
if (!empty($url['prefix'])) {
$url['prefix'] = Inflector::underscore($url['prefix']);
}
return parent::match($url, $context);
}

Expand Down
50 changes: 36 additions & 14 deletions src/Routing/Route/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,22 +227,27 @@ public function getName() {
return $this->_name;
}
$name = '';
if (isset($this->defaults['plugin'])) {
$name = $this->defaults['plugin'] . '.';
}
if (strpos($this->template, ':plugin') !== false) {
$name = '_plugin.';
}
foreach (array('controller', 'action') as $key) {
if ($key === 'action') {
$name .= ':';
}
$var = ':' . $key;
if (strpos($this->template, $var) !== false) {
$name .= '_' . $key;
$keys = [
'prefix' => ':',
'plugin' => '.',
'controller' => ':',
'action' => ''
];
foreach ($keys as $key => $glue) {
$value = null;
if (strpos($this->template, ':' . $key) !== false) {
$value = '_' . $key;
} elseif (isset($this->defaults[$key])) {
$name .= $this->defaults[$key];
$value = $this->defaults[$key];
}

if ($value === null) {
continue;
}
if (is_bool($value)) {
$value = $value ? '1' : '0';
}
$name .= $value . $glue;
}
return $this->_name = strtolower($name);
}
Expand Down Expand Up @@ -571,4 +576,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;
}

}
196 changes: 23 additions & 173 deletions src/Routing/ScopedRouteCollection.php → src/Routing/RouteBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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).
*
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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. ' .
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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);
}

/**
Expand Down
Loading

0 comments on commit 106fee0

Please sign in to comment.