<?php
/**
* File containing the Router class
*
* (PHP 5)
*
* @package PHPonTrax
* @version $Id: router.php 174 2006-03-14 04:10:15Z haas $
* @copyright (c) 2005 John Peterson
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Convert a URL to an action
* @tutorial PHPonTrax/Router.cls
*/
class Router {
/**
* Route table
*
* For a description of the structure, see
* {@tutorial PHPonTrax/Router.cls#table the Router tutorial}.
* Routes are added by calling {@link connect()} and looked up
* by calling {@link find_route()}.
* <b>FIXME:</b> Should we have a Route class to describe an
* entry in the route table?
* @var string[][]
*/
private $routes = array();
/**
* Last route found by a call to find_route()
* @var string[]
*/
private $selected_route = null;
/**
* Default route path
*
* This route path is added to the route table if the table is
* empty when find_route() is called.
* @var string constant
*/
private $default_route_path = ":controller/:action/:id";
/**
* Count of the number of elements in $routes
* @var integer
*/
public $routes_count = 0;
/**
* Accessor method to return contents of $selected_route
* @return string[] Contents of $selected_route
* @uses $selected_route
*/
function get_selected_route() {
return $this->selected_route;
}
/**
* Accessor method to add a route to the route table
*
* The route is added to the end of
* {@link $routes the route table}. If $params is not an array,
* NULL is stored in the route parameter area.
* @param string $path
* @param mixed[] $params
* @uses $routes
* @uses $routes_count
*/
function connect($path, $params = null) {
if(!is_array($params)) $params = null;
$this->routes[$this->routes_count]['path'] = $path;
$this->routes[$this->routes_count]['params'] = $params;
$this->routes_count = count($this->routes);
}
/**
* Find first route in route table with path that matches argument
*
* First, assure that the route table {@link $routes} has at
* least one route by adding
* {@link $default_route_path the default route} if the table is
* empty. Then search the table to find the first route in the
* table whose path matches the argument $url. If $url is an
* empty string, it matches a path that is an empty string.
* Otherwise, try to match $url to the path part of the table
* entry according to
* {@link http://www.php.net/manual/en/ref.pcre.php Perl regular expression}
* rules. If a matching route is found, return it any to the caller, and
* also save a copy in {@link $selected_route}; if no matching
* route is found return null.
* @param string $url
* @uses build_route_regexp()
* @uses $default_route_path
* @uses $routes
* @uses $routes_count
* @uses $selected_route
* @return mixed Matching route or null. Path is in return['path'],
* params in return['params'],
*/
function find_route($url) {
//error_log('url='.$url);
// ensure at least one route (the default route) exists
if($this->routes_count == 0) {
$this->connect($this->default_route_path);
}
$this->selected_route = null;
foreach($this->routes as $route) {
unset($route_regexp);
unset($reg_exp);
$route_regexp = $this->build_route_regexp($route['path']);
//error_log("route regexp=/$route_regexp/");
if($url == "" && $route_regexp == "") {
//error_log('selected');
$this->selected_route = $route;
break;
} elseif(preg_match("/$route_regexp/",$url) && $route_regexp != "") {
//error_log('selected');
$this->selected_route = $route;
break;
} elseif($route['path'] == $this->default_route_path) {
//error_log('defaulted');
$this->selected_route = $route;
break;
}
}
//error_log('selected route='.var_export($this->selected_route,true));
return $this->selected_route;
} // function find_route($url)
/**
* Build a regular expression that matches a route
*
* @todo <b>FIXME:</b> Should this method be private?
* @todo <b>FIXME:</b> Shouldn't the regexp match be the same as
* for a PHP variable name? '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'
* @param string $route_path A route path.
* @return string Regular expression that matches the route in
* $route_path
*/
function build_route_regexp($route_path) {
// echo "entering build_route_regexp(), \$route_path is '$route_path'\n";
$route_regexp = null;
if(!is_array($route_path)) {
$route_path = explode("/",$route_path);
}
//error_log("route path:\n".var_export($route_path,true));
if(count($route_path) > 0) {
foreach($route_path as $path_element) {
if(preg_match('/:[a-z0-9_\-]+/',$path_element)) {
$reg_exp[] = '[a-z0-9_\-]+';
} else {
$reg_exp[] = $path_element;
}
}
if(is_array($reg_exp)) {
$route_regexp = "^".implode("\/",$reg_exp)."$";
}
}
return $route_regexp;
}
}
// -- set Emacs parameters --
// Local variables:
// tab-width: 4
// c-basic-offset: 4
// c-hanging-comment-ender-p: nil
// indent-tabs-mode: nil
// End:
?>