1 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2
3 // ------------------------------------------------------------------------
4
5 /**
6 * Router Class
7 */
8 class CI_Router {
9
10 /**
11 * Config class
12 */
13 var $config;
14
15
16 /**
17 * List of routes
18
19 */
20 var $routes = array();
21
22
23 /**
24 * List of error routes
25 */
26 var $error_routes = array();
27
28
29 /**
30 * Current class name
31 */
32 var $class = '';
33
34
35 /**
36 * Current method name
37 */
38 var $method = 'index';
39
40
41 /**
42 * Sub-directory that contains the requested controller class
43 */
44 var $directory = '';
45
46
47 /**
48 * Default controller (and method if specific)
49 */
50 var $default_controller;
51
52 /**
53 * Constructor
54 */
55 function __construct()
56 {
57 $this->config =& load_class('Config', 'core');
58 $this->uri =& load_class('URI', 'core');
59 log_message('debug', "Router Class Initialized");
60 }
61
62 // --------------------------------------------------------------------
63
64 /**
65 * Set the route mapping
66 */
67 function _set_routing()
68 {
69
70 //如果项目是允许通过query_strings的形式,并且有通过$_GET的方式请求控制器的话,则以query_string形式,也就是
71 //?c=xx的形式确定路由。
72 $segments = array();
73 if ($this->config->item('enable_query_strings') === TRUE AND isset($_GET[$this->config->item('controller_trigger')]))
74 {
75 //上面这里为什么还要判断有没有通过get的方式指定控制器?其实是因为如果允许query_string的形式请求路由,但是却
76 //没有通过query_string(或者说是get)的形式指定路由的话(其实就说明这个通过query_string方式的uri是无效的),
77 //此时,我们依然会采用“段”的形式。
78
79
80 //取得目录名,目录名,控制名和方法名传递的变量名都是可以自定义的,在config/config.php里面。
81 if (isset($_GET[$this->config->item('directory_trigger')]))
82 {
83 $this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item('directory_trigger')])));
84 $segments[] = $this->fetch_directory();
85 }
86
87 //取得控制器名
88 if (isset($_GET[$this->config->item('controller_trigger')]))
89 {
90 $this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item('controller_trigger')])));
91 $segments[] = $this->fetch_class();
92 }
93
94 //取得方法名
95 if (isset($_GET[$this->config->item('function_trigger')]))
96 {
97 $this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item('function_trigger')])));
98 $segments[] = $this->fetch_method();
99 }
100 }
101 //。。。。。。。。。。。。。。。位置1
102
103 // Load the routes.php file.
104 //引入关于路由方面的配置信息。配置文件里面是一个名字为$route的数组。
105 if (defined('ENVIRONMENT') AND is_file(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
106 {
107 include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
108 }
109 elseif (is_file(APPPATH.'config/routes.php'))
110 {
111 include(APPPATH.'config/routes.php');
112 }
113
114 //下面这个莫名出现的$route变量(注意不是$this->routes哦),就是写在配置文件里面。把它copy到$this->routes。
115 //这个$routes是用来指定默认控制器和默认方法,404(请求路由不存在)后规定的路由以及一些路由重定向(?这个重写向的
116 //实现在Router::_parse_routes()中实现)的信息。
117 $this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route;
118 unset($route);//利用完就干掉。
119
120 //根据刚才的配置信息,设定默认控制器,没有的话,就为FLASE。
121 $this->default_controller = ( ! isset($this->routes['default_controller']) OR $this->routes['default_controller'] == '') ? FALSE : strtolower($this->routes['default_controller']);
122
123
124 //这个判断的位置放得有点怪,我觉得可以放到上面“位置1”的地方,下面的代码是判断刚才有没有通过query_string的方式拿到
125 //路由信息,如果拿到的话,那么就不再尝试“段”的方式确定路由了。直接确定路由,结束本函数。
126 if (count($segments) > 0)
127 {
128 //_validate_quest($segments);的作用就是确定并设置路由。
129 //这个函数执行完之后,Router::$class,Router::$directory(如果有)都会有相应值。
130 return $this->_validate_request($segments);
131 }
132
133 //下面的_fetch_uri_string()详见:URI.php,在这个位置只需知道它的作用是:
134 //从uri中检测处理,把我们确定路由需要的信息(例如“index.php/index/welcome/1”后
135 //面"index/welcome/1"这串)放到$this->uri->uri_string这个东西中。
136 $this->uri->_fetch_uri_string();
137
138 //上面_fetch_uri_string()完了之后,这个uri_string就会有我们要用的信息,如果为空的话,那么就用把路由设置为默认的。
139 if ($this->uri->uri_string == '')
140 {
141 //移步至Router::_set_default_controller();
142 return $this->_set_default_controller();
143 }
144
145
146 //如果$this->uri->uri_string 不为空,那么,会通过下面的方式确定路由
147
148 //这里只是简单地把后缀去掉而已,因为CI允许在uri后面加后缀,但它其实对我们寻找路由是多余,而且会造成影响的,所以先去掉。
149 $this->uri->_remove_url_suffix();
150
151 //把最初的uri,变成数组放在segments里面。
152 $this->uri->_explode_segments();
153
154 //开始找路由。移步至 Router::_parse_routes();
155 $this->_parse_routes();
156
157 //设置为由1开始。
158 $this->uri->_reindex_segments();
159 }
160
161 // --------------------------------------------------------------------
162
163 /**
164 * Set the default controller
165 */
166 function _set_default_controller()
167 {
168 //在Router::_set_routing()函数里面有一个操作,是从配置文件里面读取默认控制器名,如果没有就有FALSE。
169 if ($this->default_controller === FALSE)
170 {
171 //如果没有默认的话,就报错,结束程序。
172 //实质上,这个_set_default_controller()仅仅是在uri没有指定控制器,要求访问默认控制器的时候才
173 //被调用,所以如果连默认控制器都没有,那么可以果断报错。
174 show_error("Unable to determine what should be displayed. A default route has not been specified in the routing file.");
175 }
176
177 //如果有,下面我们就来把默认的控制器设置为当前要找的路由。
178
179 //这里只是分“有指定默认方法”和“没有指定”两种情况而已。不过要弄点下面那个$this->_set_request($x);CI这几个函数
180 //也许写得很妙,但是让人看得纠结。。。
181 if (strpos($this->default_controller, '/') !== FALSE)
182 {
183 $x = explode('/', $this->default_controller);
184
185 $this->set_class($x[0]);
186 $this->set_method($x[1]);
187 $this->_set_request($x);//移步至Router::_set_request();
188 }
189 else
190 {
191 $this->set_class($this->default_controller);
192 $this->set_method('index');
193 $this->_set_request(array($this->default_controller, 'index'));
194 }
195
196 // re-index the routed segments array so it starts with 1 rather than 0
197 $this->uri->_reindex_segments();
198
199 log_message('debug', "No URI present. Default controller set.");
200 }
201
202 // --------------------------------------------------------------------
203
204 /**
205 * Set the Route
206 */
207 function _set_request($segments = array())
208 {
209 /**
210 * 下面来解剖一下这个让人纠结的函数。。第一次看的时候差点被它们这几个函数搞晕。
211 */
212
213 /**
214 * 看,这里有调用Router::_validate_request();而Router::_validate_request()的作用是检测寻找出一个
215 * 正确存在的路由,并确定它,确定后的值分别放到Rouer::$class这些属性里面。所以使到这个_set_request()也有
216 * 这种确定路由的功能。
217 *
218 * 注:
219 * $segments=$this->_validate_request($segments); 等式右边,括号里面的这个$segments,也就是调用
220 * _set_request()时传入来的这个参数,它有这样的特点:
221 * 1)如果这时_set_request()是在Router::_set_default_controller()中调用的话,那个这个$segments是永远不会为
222 * 空数组,嗯,绝对不会。
223 *
224 *
225 * 而左边这个$segments的值,经过下面这行代码后,要么为空数组array(),要么为确定路由后的段数组。
226 * 为空数组的原因是,$this->_validate_request();里面没有找到当前目录的默认控制器。此时,右边的
227 * $segments要么为空,要么只指定了目录但默认控制器不存在。
228 */
229 $segments = $this->_validate_request($segments);
230
231 if (count($segments) == 0)
232 {
233 //所以如果上面返回了空数组,就会进到这里。
234 //这里居然又调回了_set_default_controller()! 坑爹吧!
235 return $this->_set_default_controller();
236 /**
237 * 我曾经想过,下面这里会不会死循环:
238 * 假如,我在配置文件里面的默认控制器设为welcome,然后controllers/下没有welcome.php,但controllers/下有
239 * welcome/有这个目录(里面没东西),然后通过http://localhost/CI/来访问默认控制器,那会怎样呢?
240 * 首先,它会进入_set_routing();然后发现$this->uri->uri_string为空,进入_set_default_controller();
241 * 然后发现在_set_default_controller里,发现$this->default_controller不为FALSE,(@@@@),然后再
242 * 进入这_set_request()里面,再进入_validate_request()里面,会不会_validate_request里返回空数组?因为
243 * 指定了目录,没有指定控制器,访问默认的,又不存在,然后返回空数组,返回空数组后,最终就会走来你正在看的这个位置,
244 * 然后这个位置再调用_set_default_controller();然后死循环了。。。
245 *
246 * 答案是不会的。
247 * 原因在于:
248 * 我们回到上面解译那个(@@@@)的地方,在这里,发现$this->default_controller不为FALSE后,它会进入这个else
249 * 里面
250 * else
251 * {
252 * $this->set_class($this->default_controller); ..............1
253 * $this->set_method('index'); ...................2
254 * $this->_set_request(array($this->default_controller, 'index')); ..........3
255 * }
256 *
257 * 然后第3行,传入_set_request($segments)中的那个$segments其实是
258 * array('welcome','index'),重点在于那个小小的'index'!!!!!!!
259 * 这样一来,我们进入_validate_request()的时候,我们实质并没有“指定目录但没有指定控制器,访问默认控制器”,
260 * 而是“指定了一个welcome的目录,和一个叫index的控制器!!”,所以才不会死循环。
261 * 如果你试着把第3行那个'index'去掉,那么,一定会死循环!!!!!!!!不信试试!CI太牛逼了,居然这样做。汗。。
262 * 当然,‘index’还有一个作用,就是设置默认方法啦。
263 */
264 }
265
266 $this->set_class($segments[0]);
267
268 if (isset($segments[1]))
269 {
270 // A standard method request
271 $this->set_method($segments[1]);
272 }
273 else
274 {
275 $segments[1] = 'index';
276 }
277
278
279 //这里要说一下,现在是在ROUTER里面为URI赋值,URI里面的这个URI::$rsegments是经过处理,并确定路由后,实质调用的路由的段信息。
280 //而URI::$segments (前面少了个r),则是原来没处理前的那个,即直接由网址上面得出来的那个。
281 $this->uri->rsegments = $segments;
282 }
283
284 // --------------------------------------------------------------------
285
286 /**
287 * Validates the supplied segments. Attempts to determine the path to
288 * the controller.
289 */
290 function _validate_request($segments)
291 {
292 if (count($segments) == 0)
293 {
294 return $segments;
295 }
296
297 if (file_exists(APPPATH.'controllers/'.$segments[0].'.php'))
298 {
299 //如果直接在controllers这个目录下找到与第一段相应的控制器名,那就说明找到了控制器,确定路由,返回。
300 return $segments;
301 }
302
303 //如果上面没有找到,再看看这个“第一段”是不是一个目录,因为CI是允许控制器放在自定义的目录下的。
304 if (is_dir(APPPATH.'controllers/'.$segments[0]))
305 {
306 // Set the directory and remove it from the segment array
307 //如果的确是目录,那么就可以确定路由的目录部分了。
308 $this->set_directory($segments[0]);
309 //去掉目录部分。进一步进行路由寻找。
310 $segments = array_slice($segments, 1);
311
312 //如果uri请求中除了目录还有其它“段”,那说明是有请求某指定控制器的。
313 if (count($segments) > 0)
314 {
315 //指定请求的控制器找不到的话,那只好报错了。
316 if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].'.php'))
317 {
318 //报错也有两方式,一种是默认的,一种是自义定的。
319 //下面这个404_override就是在config/routes.php定义的一个路由找不到时候的默认处理控制器了,如果有定义
320 //我们调用它。
321 if ( ! empty($this->routes['404_override']))
322 {
323 $x = explode('/', $this->routes['404_override']);
324
325 $this->set_directory('');//把刚才设置好的路由的目录部分去掉,因为现在路由是我们定义的404路由。
326 $this->set_class($x[0]);//这里可以看出,我们定义的404路由是不允许放在某个目录下的,只能直接放在controllers/下
327 $this->set_method(isset($x[1]) ? $x[1] : 'index');//默认是index方法
328
329 return $x; //同样,返回“段”数组
330 }
331 else
332 {
333 //默认找不到路由的方法。在core/Common.php中定义的全局函数(实质调用Exception组件进行处理)。
334 show_404($this->fetch_directory().$segments[0]);
335 }
336 }
337 }
338 else
339 {
340 //来到这里,说明了是uri请求指定了目录,而没有指定控制器的情况下。那么,我们默认当前路由是在当前目录下请求默认的
341 //控制器和方法。
342
343 //下面这个判断只是判断一下$this->default_controller有没有指定方法而已。
344 if (strpos($this->default_controller, '/') !== FALSE)
345 {
346 $x = explode('/', $this->default_controller);
347 $this->set_class($x[0]);
348 $this->set_method($x[1]);
349 }
350 else
351 {
352 $this->set_class($this->default_controller);
353 $this->set_method('index');//没有的话就默认为index方法。
354 }
355
356 // Does the default controller exist in the sub-folder?
357 //如果连默认控制器都不存在的话,就无语了,说明uri打算请求这个目录的默认控制器,结果没有这个默认控制器,那暂时
358 //返回个空数组。(但是看清楚,上面已经$this->set_class()了,说明即使没有,我们也已经把默认控制器的名字算
359 //是确定下来先)
360 if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.'.php'))
361 {
362 $this->directory = '';
363 return array();
364 }
365
366 }
367
368 //能够有命来到这一步,是说明的确是某个目录下找到了控制器,或者是找到了定义的默认控制器。
369 //但是注意,这个$segments返回的“段”信息都是不包括目录的。它是一个数组形式,第一个元素是控制器名。
370 //例如:array('acontroller','amethod','xx','xx')。。
371 return $segments;
372 }
373
374 //来到这里,就说明了,即找不到controllers/下相应的控制器,也找不到这样的目录。那就报错咯。
375 if ( ! empty($this->routes['404_override']))
376 {
377 $x = explode('/', $this->routes['404_override']);
378
379 $this->set_class($x[0]);
380 $this->set_method(isset($x[1]) ? $x[1] : 'index');
381
382 return $x;
383 }
384
385
386 // Nothing else to do at this point but show a 404
387 show_404($segments[0]);
388 }
389
390 // --------------------------------------------------------------------
391
392 /**
393 * Parse Routes
394 */
395 function _parse_routes()
396 {
397 // Turn the segment array into a URI string
398 //知道_set_request()是干嘛的之后,下面的条理就比较清晰了。
399 $uri = implode('/', $this->uri->segments);
400
401 // Is there a literal match? If so we're done
402 if (isset($this->routes[$uri]))
403 {
404 return $this->_set_request(explode('/', $this->routes[$uri]));
405 }
406
407 /**
408 * CI有路由重定向的功能,重定向的规则和实现就是在这里。
409 */
410 foreach ($this->routes as $key => $val)
411 {
412 $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key));
413
414
415 if (preg_match('#^'.$key.'$#', $uri))
416 {
417 // Do we have a back-reference?
418 if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
419 {
420 $val = preg_replace('#^'.$key.'$#', $val, $uri);
421 }
422
423 return $this->_set_request(explode('/', $val));
424 }
425 }
426 $this->_set_request($this->uri->segments);
427 }
428
429 // --------------------------------------------------------------------
430
431 /**
432 * Set the class name
433 */
434 function set_class($class)
435 {
436 $this->class = str_replace(array('/', '.'), '', $class);
437 }
438
439 // --------------------------------------------------------------------
440
441 /**
442 * Fetch the current class
443 */
444 function fetch_class()
445 {
446 return $this->class;
447 }
448
449 // --------------------------------------------------------------------
450
451 /**
452 * Set the method name
453 */
454 function set_method($method)
455 {
456 $this->method = $method;
457 }
458
459 // --------------------------------------------------------------------
460
461 /**
462 * Fetch the current method
463 */
464 function fetch_method()
465 {
466 if ($this->method == $this->fetch_class())
467 {
468 return 'index';
469 }
470
471 return $this->method;
472 }
473
474 // --------------------------------------------------------------------
475
476 /**
477 * Set the directory name
478 */
479 function set_directory($dir)
480 {
481 $this->directory = str_replace(array('/', '.'), '', $dir).'/';
482 }
483
484 // --------------------------------------------------------------------
485
486 /**
487 * Fetch the sub-directory (if any) that contains the requested controller class
488 */
489 function fetch_directory()
490 {
491 return $this->directory;
492 }
493
494 // --------------------------------------------------------------------
495
496 /**
497 * Set the controller overrides
498 */
499 function _set_overrides($routing)
500 {
501 if ( ! is_array($routing))
502 {
503 return;
504 }
505
506 if (isset($routing['directory']))
507 {
508 $this->set_directory($routing['directory']);
509 }
510
511 if (isset($routing['controller']) AND $routing['controller'] != '')
512 {
513 $this->set_class($routing['controller']);
514 }
515
516 if (isset($routing['function']))
517 {
518 $routing['function'] = ($routing['function'] == '') ? 'index' : $routing['function'];
519 $this->set_method($routing['function']);
520 }
521 }
522
523
524 }