| 1 | <?php |
|---|
| 2 | /** |
|---|
| 3 | * File containing the Router class |
|---|
| 4 | * |
|---|
| 5 | * (PHP 5) |
|---|
| 6 | * |
|---|
| 7 | * @package PHPonTrax |
|---|
| 8 | * @version $Id$ |
|---|
| 9 | * @copyright (c) 2005 John Peterson |
|---|
| 10 | * |
|---|
| 11 | * Permission is hereby granted, free of charge, to any person obtaining |
|---|
| 12 | * a copy of this software and associated documentation files (the |
|---|
| 13 | * "Software"), to deal in the Software without restriction, including |
|---|
| 14 | * without limitation the rights to use, copy, modify, merge, publish, |
|---|
| 15 | * distribute, sublicense, and/or sell copies of the Software, and to |
|---|
| 16 | * permit persons to whom the Software is furnished to do so, subject to |
|---|
| 17 | * the following conditions: |
|---|
| 18 | * |
|---|
| 19 | * The above copyright notice and this permission notice shall be |
|---|
| 20 | * included in all copies or substantial portions of the Software. |
|---|
| 21 | * |
|---|
| 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|---|
| 23 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|---|
| 24 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|---|
| 25 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|---|
| 26 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|---|
| 27 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|---|
| 28 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|---|
| 29 | */ |
|---|
| 30 | |
|---|
| 31 | /** |
|---|
| 32 | * Convert a URL to an action |
|---|
| 33 | * @tutorial PHPonTrax/Router.cls |
|---|
| 34 | */ |
|---|
| 35 | class Router { |
|---|
| 36 | |
|---|
| 37 | /** |
|---|
| 38 | * Route table |
|---|
| 39 | * |
|---|
| 40 | * For a description of the structure, see |
|---|
| 41 | * {@tutorial PHPonTrax/Router.cls#table the Router tutorial}. |
|---|
| 42 | * Routes are added by calling {@link connect()} and looked up |
|---|
| 43 | * by calling {@link find_route()}. |
|---|
| 44 | * <b>FIXME:</b> Should we have a Route class to describe an |
|---|
| 45 | * entry in the route table? |
|---|
| 46 | * @var string[][] |
|---|
| 47 | */ |
|---|
| 48 | private $routes = array(); |
|---|
| 49 | |
|---|
| 50 | /** |
|---|
| 51 | * Last route found by a call to find_route() |
|---|
| 52 | * @var string[] |
|---|
| 53 | */ |
|---|
| 54 | private $selected_route = null; |
|---|
| 55 | |
|---|
| 56 | /** |
|---|
| 57 | * Default route path |
|---|
| 58 | * |
|---|
| 59 | * This route path is added to the route table if the table is |
|---|
| 60 | * empty when find_route() is called. |
|---|
| 61 | * @var string constant |
|---|
| 62 | */ |
|---|
| 63 | private $default_route_path = ":controller/:action/:id"; |
|---|
| 64 | |
|---|
| 65 | /** |
|---|
| 66 | * Count of the number of elements in $routes |
|---|
| 67 | * @var integer |
|---|
| 68 | */ |
|---|
| 69 | public $routes_count = 0; |
|---|
| 70 | |
|---|
| 71 | /** |
|---|
| 72 | * Accessor method to return contents of $selected_route |
|---|
| 73 | * @return string[] Contents of $selected_route |
|---|
| 74 | * @uses $selected_route |
|---|
| 75 | */ |
|---|
| 76 | function get_selected_route() { |
|---|
| 77 | return $this->selected_route; |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | /** |
|---|
| 81 | * Accessor method to add a route to the route table |
|---|
| 82 | * |
|---|
| 83 | * The route is added to the end of |
|---|
| 84 | * {@link $routes the route table}. If $params is not an array, |
|---|
| 85 | * NULL is stored in the route parameter area. |
|---|
| 86 | * @param string $path |
|---|
| 87 | * @param mixed[] $params |
|---|
| 88 | * @uses $routes |
|---|
| 89 | * @uses $routes_count |
|---|
| 90 | */ |
|---|
| 91 | function connect($path, $params = null) { |
|---|
| 92 | if(!is_array($params)) $params = null; |
|---|
| 93 | $this->routes[$this->routes_count]['path'] = $path; |
|---|
| 94 | $this->routes[$this->routes_count]['params'] = $params; |
|---|
| 95 | $this->routes_count = count($this->routes); |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | /** |
|---|
| 99 | * Find first route in route table with path that matches argument |
|---|
| 100 | * |
|---|
| 101 | * First, assure that the route table {@link $routes} has at |
|---|
| 102 | * least one route by adding |
|---|
| 103 | * {@link $default_route_path the default route} if the table is |
|---|
| 104 | * empty. Then search the table to find the first route in the |
|---|
| 105 | * table whose path matches the argument $url. If $url is an |
|---|
| 106 | * empty string, it matches a path that is an empty string. |
|---|
| 107 | * Otherwise, try to match $url to the path part of the table |
|---|
| 108 | * entry according to |
|---|
| 109 | * {@link http://www.php.net/manual/en/ref.pcre.php Perl regular expression} |
|---|
| 110 | * rules. If a matching route is found, return it any to the caller, and |
|---|
| 111 | * also save a copy in {@link $selected_route}; if no matching |
|---|
| 112 | * route is found return null. |
|---|
| 113 | * @param string $url |
|---|
| 114 | * @uses build_route_regexp() |
|---|
| 115 | * @uses $default_route_path |
|---|
| 116 | * @uses $routes |
|---|
| 117 | * @uses $routes_count |
|---|
| 118 | * @uses $selected_route |
|---|
| 119 | * @return mixed Matching route or null. Path is in return['path'], |
|---|
| 120 | * params in return['params'], |
|---|
| 121 | */ |
|---|
| 122 | function find_route($url) { |
|---|
| 123 | //error_log('url='.$url); |
|---|
| 124 | // ensure at least one route (the default route) exists |
|---|
| 125 | if($this->routes_count == 0) { |
|---|
| 126 | $this->connect($this->default_route_path); |
|---|
| 127 | } |
|---|
| 128 | |
|---|
| 129 | $this->selected_route = null; |
|---|
| 130 | |
|---|
| 131 | foreach($this->routes as $route) { |
|---|
| 132 | unset($route_regexp); |
|---|
| 133 | unset($reg_exp); |
|---|
| 134 | $route_regexp = $this->build_route_regexp($route['path']); |
|---|
| 135 | //error_log("route regexp=/$route_regexp/"); |
|---|
| 136 | if($url == "" && $route_regexp == "") { |
|---|
| 137 | //error_log('selected'); |
|---|
| 138 | $this->selected_route = $route; |
|---|
| 139 | break; |
|---|
| 140 | } elseif(preg_match("/$route_regexp/",$url) && $route_regexp != "") { |
|---|
| 141 | //error_log('selected'); |
|---|
| 142 | $this->selected_route = $route; |
|---|
| 143 | break; |
|---|
| 144 | } elseif($route['path'] == $this->default_route_path) { |
|---|
| 145 | //error_log('defaulted'); |
|---|
| 146 | $this->selected_route = $route; |
|---|
| 147 | break; |
|---|
| 148 | } |
|---|
| 149 | } |
|---|
| 150 | //error_log('selected route='.var_export($this->selected_route,true)); |
|---|
| 151 | return $this->selected_route; |
|---|
| 152 | } // function find_route($url) |
|---|
| 153 | |
|---|
| 154 | /** |
|---|
| 155 | * Build a regular expression that matches a route |
|---|
| 156 | * |
|---|
| 157 | * @todo <b>FIXME:</b> Should this method be private? |
|---|
| 158 | * @todo <b>FIXME:</b> Shouldn't the regexp match be the same as |
|---|
| 159 | * for a PHP variable name? '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*' |
|---|
| 160 | * @param string $route_path A route path. |
|---|
| 161 | * @return string Regular expression that matches the route in |
|---|
| 162 | * $route_path |
|---|
| 163 | */ |
|---|
| 164 | function build_route_regexp($route_path) { |
|---|
| 165 | // echo "entering build_route_regexp(), \$route_path is '$route_path'\n"; |
|---|
| 166 | |
|---|
| 167 | $route_regexp = null; |
|---|
| 168 | |
|---|
| 169 | if(!is_array($route_path)) { |
|---|
| 170 | $route_path = explode("/",$route_path); |
|---|
| 171 | } |
|---|
| 172 | //error_log("route path:\n".var_export($route_path,true)); |
|---|
| 173 | if(count($route_path) > 0) { |
|---|
| 174 | foreach($route_path as $path_element) { |
|---|
| 175 | if(preg_match('/:[a-z0-9_\-]+/',$path_element)) { |
|---|
| 176 | $reg_exp[] = '[a-z0-9_\-]+'; |
|---|
| 177 | } else { |
|---|
| 178 | $reg_exp[] = $path_element; |
|---|
| 179 | } |
|---|
| 180 | } |
|---|
| 181 | if(is_array($reg_exp)) { |
|---|
| 182 | $route_regexp = "^".implode("\/",$reg_exp)."$"; |
|---|
| 183 | } |
|---|
| 184 | } |
|---|
| 185 | return $route_regexp; |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | } |
|---|
| 189 | |
|---|
| 190 | // -- set Emacs parameters -- |
|---|
| 191 | // Local variables: |
|---|
| 192 | // tab-width: 4 |
|---|
| 193 | // c-basic-offset: 4 |
|---|
| 194 | // c-hanging-comment-ender-p: nil |
|---|
| 195 | // indent-tabs-mode: nil |
|---|
| 196 | // End: |
|---|
| 197 | ?> |
|---|