forked from joomla/joomla-platform
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request joomla#1259 from LouisLandry/router
New (and generic) web application routers.
- Loading branch information
Showing
9 changed files
with
1,017 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
<?php | ||
/** | ||
* @package Joomla.Platform | ||
* @subpackage Application | ||
* | ||
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved. | ||
* @license GNU General Public License version 2 or later; see LICENSE | ||
*/ | ||
|
||
defined('JPATH_PLATFORM') or die; | ||
|
||
/** | ||
* Class to define an abstract Web application router. | ||
* | ||
* @package Joomla.Platform | ||
* @subpackage Application | ||
* @since 12.3 | ||
*/ | ||
abstract class JApplicationWebRouter | ||
{ | ||
/** | ||
* @var JApplicationWeb The web application on whose behalf we are routing the request. | ||
* @since 12.3 | ||
*/ | ||
protected $app; | ||
|
||
/** | ||
* @var string The default page controller name for an empty route. | ||
* @since 12.3 | ||
*/ | ||
protected $default; | ||
|
||
/** | ||
* @var string Controller class name prefix for creating controller objects by name. | ||
* @since 12.3 | ||
*/ | ||
protected $controllerPrefix; | ||
|
||
/** | ||
* @var JInput An input object from which to derive the route. | ||
* @since 12.3 | ||
*/ | ||
protected $input; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param JApplicationWeb $app The web application on whose behalf we are routing the request. | ||
* @param JInput $input An optional input object from which to derive the route. If none | ||
* is given than the input from the application object will be used. | ||
* | ||
* @since 12.3 | ||
*/ | ||
public function __construct(JApplicationWeb $app, JInput $input = null) | ||
{ | ||
$this->app = $app; | ||
$this->input = ($input === null) ? $this->app->input : $input; | ||
} | ||
|
||
/** | ||
* Find and execute the appropriate controller based on a given route. | ||
* | ||
* @param string $route The route string for which to find and execute a controller. | ||
* | ||
* @return void | ||
* | ||
* @since 12.3 | ||
* @throws InvalidArgumentException | ||
* @throws RuntimeException | ||
*/ | ||
public function execute($route) | ||
{ | ||
// Get the controller name based on the route patterns and requested route. | ||
$name = $this->parseRoute($route); | ||
|
||
// Get the controller object by name. | ||
$controller = $this->fetchController($name); | ||
|
||
// Execute the controller. | ||
$controller->execute(); | ||
} | ||
|
||
/** | ||
* Set the controller name prefix. | ||
* | ||
* @param string $prefix Controller class name prefix for creating controller objects by name. | ||
* | ||
* @return JApplicationWebRouter This object for method chaining. | ||
* | ||
* @since 12.3 | ||
*/ | ||
public function setControllerPrefix($prefix) | ||
{ | ||
$this->controllerPrefix = (string) $prefix; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Set the default controller name. | ||
* | ||
* @param string $name The default page controller name for an empty route. | ||
* | ||
* @return JApplicationWebRouter This object for method chaining. | ||
* | ||
* @since 12.3 | ||
*/ | ||
public function setDefaultController($name) | ||
{ | ||
$this->default = (string) $name; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Parse the given route and return the name of a controller mapped to the given route. | ||
* | ||
* @param string $route The route string for which to find and execute a controller. | ||
* | ||
* @return string The controller name for the given route excluding prefix. | ||
* | ||
* @since 12.3 | ||
* @throws InvalidArgumentException | ||
*/ | ||
abstract protected function parseRoute($route); | ||
|
||
/** | ||
* Get a JController object for a given name. | ||
* | ||
* @param string $name The controller name (excluding prefix) for which to fetch and instance. | ||
* | ||
* @return JController | ||
* | ||
* @since 12.3 | ||
* @throws RuntimeException | ||
*/ | ||
protected function fetchController($name) | ||
{ | ||
// Derive the controller class name. | ||
$class = $this->controllerPrefix . ucfirst($name); | ||
|
||
// If the controller class does not exist panic. | ||
if (!class_exists($class) || !is_subclass_of($class, 'JController')) | ||
{ | ||
throw new RuntimeException(sprintf('Unable to locate controller `%s`.', $class), 404); | ||
} | ||
|
||
// Instantiate the controller. | ||
$controller = new $class($this->input, $this->app); | ||
|
||
return $controller; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
<?php | ||
/** | ||
* @package Joomla.Platform | ||
* @subpackage Application | ||
* | ||
* @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved. | ||
* @license GNU General Public License version 2 or later; see LICENSE | ||
*/ | ||
|
||
defined('JPATH_PLATFORM') or die; | ||
|
||
/** | ||
* Basic Web application router class for the Joomla Platform. | ||
* | ||
* @package Joomla.Platform | ||
* @subpackage Application | ||
* @since 12.3 | ||
*/ | ||
class JApplicationWebRouterBase extends JApplicationWebRouter | ||
{ | ||
/** | ||
* @var array An array of rules, each rule being an associative array('regex'=> $regex, 'vars' => $vars, 'controller' => $controller) | ||
* for routing the request. | ||
* @since 12.3 | ||
*/ | ||
protected $maps = array(); | ||
|
||
/** | ||
* Add a route map to the router. If the pattern already exists it will be overwritten. | ||
* | ||
* @param string $pattern The route pattern to use for matching. | ||
* @param string $controller The controller name to map to the given pattern. | ||
* | ||
* @return JApplicationWebRouter This object for method chaining. | ||
* | ||
* @since 12.3 | ||
*/ | ||
public function addMap($pattern, $controller) | ||
{ | ||
// Sanitize and explode the pattern. | ||
$pattern = explode('/', trim(parse_url((string) $pattern, PHP_URL_PATH), ' /')); | ||
|
||
// Prepare the route variables | ||
$vars = array(); | ||
|
||
// Initialize regular expression | ||
$regex = array(); | ||
|
||
// Loop on each segment | ||
foreach ($pattern as $segment) | ||
{ | ||
// Match a splat with no variable. | ||
if ($segment == '*') | ||
{ | ||
$regex[] = '.*'; | ||
} | ||
// Match a splat and capture the data to a named variable. | ||
elseif ($segment[0] == '*') | ||
{ | ||
$vars[] = substr($segment, 1); | ||
$regex[] = '(.*)'; | ||
} | ||
// Match an escaped splat segment. | ||
elseif ($segment[0] == '\\' && $segment[1] == '*') | ||
{ | ||
$regex[] = '\*' . preg_quote(substr($segment, 2)); | ||
} | ||
// Match an unnamed variable without capture. | ||
elseif ($segment == ':') | ||
{ | ||
$regex[] = '[^/]*'; | ||
} | ||
// Match a named variable and capture the data. | ||
elseif ($segment[0] == ':') | ||
{ | ||
$vars[] = substr($segment, 1); | ||
$regex[] = '([^/]*)'; | ||
} | ||
// Match a semgent with an escaped variable character prefix. | ||
elseif ($segment[0] == '\\' && $segment[1] == ':') | ||
{ | ||
$regex[] = preg_quote(substr($segment, 1)); | ||
} | ||
// Match the standard segment. | ||
else | ||
{ | ||
$regex[] = preg_quote($segment); | ||
} | ||
} | ||
|
||
$this->maps[] = array( | ||
'regex' => chr(1) . '^' . implode('/', $regex) . '$' . chr(1), | ||
'vars' => $vars, | ||
'controller' => (string) $controller | ||
); | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Add a route map to the router. If the pattern already exists it will be overwritten. | ||
* | ||
* @param array $maps A list of route maps to add to the router as $pattern => $controller. | ||
* | ||
* @return JApplicationWebRouter This object for method chaining. | ||
* | ||
* @since 12.3 | ||
*/ | ||
public function addMaps($maps) | ||
{ | ||
foreach ($maps as $pattern => $controller) | ||
{ | ||
$this->addMap($pattern, $controller); | ||
} | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Parse the given route and return the name of a controller mapped to the given route. | ||
* | ||
* @param string $route The route string for which to find and execute a controller. | ||
* | ||
* @return string The controller name for the given route excluding prefix. | ||
* | ||
* @since 12.3 | ||
* @throws InvalidArgumentException | ||
*/ | ||
protected function parseRoute($route) | ||
{ | ||
// Initialize variables. | ||
$controller = false; | ||
|
||
// Sanitize and explode the route. | ||
$route = trim(parse_url($route, PHP_URL_PATH), ' /'); | ||
|
||
// If the route is empty then simply return the default route. No parsing necessary. | ||
if ($route == '') | ||
{ | ||
return $this->default; | ||
} | ||
|
||
// Iterate through all of the known route maps looking for a match. | ||
foreach ($this->maps as $rule) | ||
{ | ||
if (preg_match($rule['regex'], $route, $matches)) | ||
{ | ||
// If we have gotten this far then we have a positive match. | ||
$controller = $rule['controller']; | ||
|
||
// Time to set the input variables. | ||
// We are only going to set them if they don't already exist to avoid overwriting things. | ||
foreach ($rule['vars'] as $i => $var) | ||
{ | ||
$this->input->def($var, $matches[$i + 1]); | ||
|
||
// Don't forget to do an explicit set on the GET superglobal. | ||
$this->input->get->def($var, $matches[$i + 1]); | ||
} | ||
|
||
break; | ||
} | ||
} | ||
|
||
// We were unable to find a route match for the request. Panic. | ||
if (!$controller) | ||
{ | ||
throw new InvalidArgumentException(sprintf('Unable to handle request for route `%s`.', $route), 404); | ||
} | ||
|
||
return $controller; | ||
} | ||
} |
Oops, something went wrong.