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 }