zoukankan      html  css  js  c++  java
  • 五)CodeIgniter源码分析之Router.php

      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 }
  • 相关阅读:
    Android H5混合开发(5):封装Cordova View, 让Fragment、弹框、Activity自由使用Cordova
    机器学习 AI 谷歌ML Kit 与苹果Core ML
    Android 设备唯一标识(多种实现方案)
    Android自定义控件:图形报表的实现(折线图、曲线图、动态曲线图)(View与SurfaceView分别实现图表控件)
    Android 禁止截屏、录屏 — 解决PopupWindow无法禁止录屏问题
    Android原生PDF功能实现:PDF阅读、PDF页面跳转、PDF手势伸缩、PDF目录树、PDF预览缩略图
    某助手验证码通信交互包分析
    IOS弓箭传说的插件开发
    微店APP协议简要分析
    Owhat sign参数分析
  • 原文地址:https://www.cnblogs.com/qxbj/p/4415206.html
Copyright © 2011-2022 走看看