Skip to content

Commit

Permalink
Merge pull request joomla#1259 from LouisLandry/router
Browse files Browse the repository at this point in the history
New (and generic) web application routers.
  • Loading branch information
ianmacl committed Jun 12, 2012
2 parents deeff62 + cd9d941 commit 8390cef
Show file tree
Hide file tree
Showing 9 changed files with 1,017 additions and 0 deletions.
153 changes: 153 additions & 0 deletions libraries/joomla/application/web/router.php
@@ -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;
}
}
173 changes: 173 additions & 0 deletions libraries/joomla/application/web/router/base.php
@@ -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;
}
}

0 comments on commit 8390cef

Please sign in to comment.