Add info about mod_rewrite for BSRouter
[isso.git] / Router.php
1 <?php
2 /*=====================================================================*
3 || ###################################################################
4 || # Blue Static ISSO Framework [#]issoversion[#]
5 || # Copyright ©2002-[#]year[#] Blue Static
6 || #
7 || # This program is free software; you can redistribute it and/or modify
8 || # it under the terms of the GNU General Public License as published by
9 || # the Free Software Foundation; version [#]gpl[#] of the License.
10 || #
11 || # This program is distributed in the hope that it will be useful, but
12 || # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 || # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 || # more details.
15 || #
16 || # You should have received a copy of the GNU General Public License along
17 || # with this program; if not, write to the Free Software Foundation, Inc.,
18 || # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 || ###################################################################
20 \*=====================================================================*/
21
22 /**
23 * Request Router (Router.php)
24 *
25 * @package ISSO
26 */
27
28 require_once('ISSO/Functions.php');
29 require_once('ISSO/RouterController.php');
30
31 /**
32 * Router
33 *
34 * Run dispatch() in a file with a defined routing pattern and send all
35 * requests to that file, this will then load and appropriatley run
36 * the right source files. You can use mod_rewrite like this:
37 *
38 * RewriteEngine on
39 * RewriteCond %{REQUEST_URI} !static/(.*)$
40 * RewriteRule ^(.*)$ index.php
41 *
42 * This will prevent the rewriting on any file in the static/ directory
43 *
44 * @author Blue Static
45 * @copyright Copyright (c)2002 - [#]year[#], Blue Static
46 * @version $Revision$
47 * @package ISSO
48 *
49 */
50 class BSRouter
51 {
52 const MAP_LOADER = 1;
53 const MAP_PARAMS = 2;
54
55 /**
56 * Routing map of request:(controller,:method,args...)
57 * @var array
58 */
59 private $map = array();
60
61 /**
62 * The base URL
63 * @var string
64 */
65 private $basePath;
66
67 /**
68 * Map action for errors
69 * @var string
70 */
71 private $errorAction = ':undefined:';
72
73 /**
74 * Router controller requested
75 * @var string
76 */
77 private $controller;
78
79 /**
80 * Router action requsted
81 * @var string
82 */
83 private $action;
84
85 /**
86 * Request paramaters
87 * @var array
88 */
89 private $params = array();
90
91 // ###################################################################
92 /**
93 * Constructor
94 */
95 public function __construct()
96 {
97 $this->basePath = str_replace(basename($_SERVER['PHP_SELF']), '', $_SERVER['SCRIPT_NAME']);
98 }
99
100 // ###################################################################
101 /**
102 * Listens for routing requests and handles them appropriately
103 */
104 public function dispatch()
105 {
106 $request = str_replace($this->basePath, '', $_SERVER['REQUEST_URI']);
107
108 $params = explode('/', $request);
109 $request = explode('.', $params[0]);
110 $this->request = $request[0];
111 $this->action = ((!isset($request[1]) OR empty($request[1])) ? 'Index' : $request[1]);
112 unset($params[0]);
113
114 if (sizeof($params) == 0)
115 {
116 // do nothing
117 }
118 else if (sizeof($params) == 1)
119 {
120 $this->params[':id'] = $params[1];
121 }
122 else
123 {
124 $key = '';
125 for ($i = 1; $i <= sizeof($params); $i++)
126 {
127 if ($i == 1 AND $params["$i"][0] != '+')
128 {
129 $this->params[':id'] = $params["$i"];
130 continue;
131 }
132
133 if ($params["$i"] == '')
134 {
135 continue;
136 }
137
138 if ($params["$i"][0] == '+')
139 {
140 if (!empty($key))
141 {
142 $this->_error(sprintf(_('The paramter "%1$s" does not have a value'), $key));
143 }
144 $key = substr($params["$i"], 1);
145 }
146 else
147 {
148 if (empty($key))
149 {
150 $this->_error(sprintf(_('The value "%1$s" does not have a paramatized key'), $params["$i"]));
151 }
152 $this->params["$key"] = $params["$i"];
153 $key = '';
154 }
155 }
156 }
157
158 if (isset($this->map[ $this->request ]))
159 {
160 call_user_func_array(array($this, $this->map[ $this->request ][self::MAP_LOADER]), $this->map[ $this->request ][self::MAP_PARAMS]);
161 }
162 else
163 {
164 $this->_error(sprintf(_('Routing request "%1$s" not found in map'), $this->request));
165 }
166 }
167
168 // ###################################################################
169 /**
170 * Sets the error handler of the router; when an error occurs while
171 * routing, this method is called
172 *
173 * @param string Error handler name
174 */
175 public function setErrorAction($name)
176 {
177 $this->errorAction = $name;
178 }
179
180 // ###################################################################
181 /**
182 * Adds a directory of files to the maps, with the file names (without .php)
183 * as the map request name
184 *
185 * @param string Directory path
186 */
187 public function addFileDirectory($path)
188 {
189 $path = BSFunctions::FetchSourcePath($path);
190 $controllers = BSFunctions::ScanDirectory($path);
191
192 foreach ($controllers AS $file)
193 {
194 $controller = str_replace('.php', '', $file);
195 $this->addFileRequest($controller, $path . $file);
196 }
197 }
198
199 // ###################################################################
200 /**
201 * Routes a request to the loading of a file; this is the most common
202 * routing request and it performs no extra functions
203 *
204 * @param string Request name
205 * @param string File name
206 */
207 public function addFileRequest($request, $file)
208 {
209 $this->map["$request"] = array(self::MAP_LOADER => '_loadFile', self::MAP_PARAMS => array($file));
210 }
211
212 // ###################################################################
213 /**
214 * Adds a list of files to the mapping; the controller will become
215 * the file name without the .php extension
216 *
217 * @param string Search path
218 */
219 public function addControllerDirectory($path)
220 {
221 $path = BSFunctions::FetchSourcePath($path);
222 $controllers = BSFunctions::ScanDirectory($path);
223
224 foreach ($controllers AS $file)
225 {
226 $controller = str_replace('.php', '', $file);
227 $this->addController($controller, $path . $controller);
228 }
229 }
230
231 // ###################################################################
232 /**
233 * Routes a request to the controller system
234 *
235 * @param string Request name
236 * @param string Controller name; the file must be named <X>.php and define a class <X>Controller.php
237 */
238 public function addController($request, $file)
239 {
240 $this->map["$request"] = array(self::MAP_LOADER => '_invokeController', self::MAP_PARAMS => array($file));
241 }
242
243 // ###################################################################
244 /**
245 * Includes a given file name
246 *
247 * @param string File name
248 */
249 private function _loadFile($filename)
250 {
251 if (!file_exists($filename))
252 {
253 $this->_error(sprintf(_('Cannot find the file "%1$s"'), $filename));
254 }
255 include($filename);
256 }
257
258 // ###################################################################
259 /**
260 * Loads and then invokes a controller
261 *
262 * @param string File name of the controller
263 */
264 public function _invokeController($controller)
265 {
266 $this->_loadFile($controller);
267
268 $controllerName = basename($controller, '.php');
269 $className = "{$controllerName}Controller";
270
271 if (!class_exists($className))
272 {
273 trigger_error('The controller "' . $className . '" does not exist and therfore cannot have actions forwarded to it');
274 }
275
276 if (!is_subclass_of($className, 'BSRouterController'))
277 {
278 trigger_error('Cannot invoke controller-style actions on a non-controller: requested controller is not of the type BSRouterController');
279 }
280
281 // these go out the door so BSInput will process them and merge them into BSInput->in[]
282 $_GET = $this->params;
283 $request = new $className(BSRegister::LoadModule('Input'));
284
285 if (!method_exists($className, $this->action))
286 {
287 trigger_error('Invalid action called on controller: ' . $className . ' does not respond to ' . $this->action . '()');
288 }
289
290 $request->{$this->action}();
291 }
292
293 // ###################################################################
294 /**
295 * Sends an error to the error action handler
296 *
297 * @param string Error message
298 */
299 private function _error($errMsg)
300 {
301 if (function_exists($this->errorAction) OR (is_array($this->errorAction) AND method_exists($this->errorAction[0], $this->errorAction[1])))
302 {
303 $this->errorAction($errMsg);
304 }
305 else
306 {
307 trigger_error($errMsg);
308 }
309 exit;
310 }
311 }
312
313 /*=====================================================================*
314 || ###################################################################
315 || # $HeadURL$
316 || # $Id$
317 || ###################################################################
318 \*=====================================================================*/
319 ?>