CodeIgniter源码分析[4]——地址解析类URI.php

在CI框架中完成地址解析的是URI.php文件,地址解析是CI框架为了识别不同风格的URL进行的配置和预处理类,针对配置信息对URL进行预处理然后进行路由。

用户配置

配置类中影响地址解析的配置项包括
$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-‘,取值用正则表达式表示URL字符串中允许出现的字符,不允许出现的字符会被过滤;
$config['url_surfix']表示URL后缀,定义次配置,CI框架会在展示给用户的URL上添加后缀,当然在地址解析时就需要去掉后缀;
$config['enable_query_strings’],表示是否允许查询字符串形式的URL,其取值为True或False。
CI默认用户使用搜索引擎友好的URL格式,比如example.com/who/what/where,但是也允许用户选择查询query_string形式的URL,比如example.com?who=me&what=something&where=here开启条件是将$config['enable_query_strings’]的值设置为True,此时URI类将不做任何处理,ROUTER类也只会根据查询字符串来匹配目录、控制器、方法。

下面分析$config['enable_query_strings’]取值为False的情况,此时$config['uri_protocol’]配置型也会影响对URL的解析方法。
$config['uri_protocol’],其取值范为REQUEST_URI,QUERT_STRING,PATH_INFO
这项配置是为了应对用户的不同的URL风格,对服务器的影响是选择取回URL字符串的方式不同,具体信息如下所示:

  • REQUEST_URI 使用的是$_SERVER['REQUEST_URI’] ,返回的是用户访问地址,即被访问的文件之后的所有URI段 [包括path_info和query_string]。
  • QUERY_STRING 使用的是$_SERVER['QUERY_STRING’],返回查询字符串即符号?之后的URL部分。
  • PATH_INFO 使用的是$_SERVER['PATH_INFO’],返回真实脚本文件之后和查询字符串之前的URL部分。
    有关$_SERVER的知识不清楚建议查阅PHP手册或相关博文

至此,我们已经清楚了钩子的开启和配置方法,下面我们开始分析CI_URI类的代码,由于方法比较多且具部分方法比较简单并且也只在该类的其他方法中使用,因此本文只就个人理解的重点方法进行介绍。

属性概览

属性名称 注释
public $keyval = array() 用缓存保存的URL字符列表
public $uri_string = '' 当前的URL字符
public $segments = array() 保存URL字符列表,数组下标从1开始
public $rsegments = array() 保存路由的URL字符列表
protected $_permitted_uri_chars 保存允许的URL字符集合

方法概览

方法名称 注释
__construct() 构造函数
_set_uri_string($str) 保存解析后的uri并设置segements数组
_parse_request_uri() request_uri配置下的解析方法
_parse_query_string() query_string配置下的解析方法
_parse_argv() cli模式下参数处理方法
_remove_relative_directory($uri) 去除相对路径和多个斜线
filter_uri(&$str) 安全处理过滤不允许的字符
segment($n, $no_result = NULL)
rsegment($n, $no_result = NULL)
uri_to_assoc($n = 3, $default = array())
ruri_to_assoc($n = 3, $default = array())
_uri_to_assoc($n = 3, $default = array(), $which = 'segment')
assoc_to_uri($array)
slash_segment($n, $where = 'trailing')
slash_rsegment($n, $where = 'trailing')
_slash_segment($n, $where = 'trailing', $which = 'segment')
segment_array()
rsegment_array()
total_segments()
total_rsegments()
uri_string()
ruri_string()

构造函数__construct

public function __construct()
{
   $this->config =& load_class('Config', 'core');
   //如果配置项中config['enable_query_string']取值为True,我们不需要处理当前的URL
   //但是这项配置在CLI模式下不生效,因此要处理CLI模式和不允许query_string的情况
   if (is_cli() OR $this->config->item('enable_query_strings') !== TRUE)
   {
       //从配置项中获取允许字符集合的正则
      $this->_permitted_uri_chars = $this->config->item('permitted_uri_chars');
      //如果是CLI模式忽略配置,将参数拼接城argv1/argv2的形式
      if (is_cli())
      {
         $uri = $this->_parse_argv();
      }
      else
      {
          //不允许query_string时,根据配置的uri_protocol选择取回的字符串,默认为REQUEST_URI
         $protocol = $this->config->item('uri_protocol');
         empty($protocol) && $protocol = 'REQUEST_URI';
         //根据配置项的不同,选择不同的函数处理URL
         switch ($protocol)
         {
            case 'AUTO': // For BC purposes only
            case 'REQUEST_URI':
               $uri = $this->_parse_request_uri();
               break;
            case 'QUERY_STRING':
               $uri = $this->_parse_query_string();
               break;
            case 'PATH_INFO':
            default:
               $uri = isset($_SERVER[$protocol])
                  ? $_SERVER[$protocol]
                  : $this->_parse_request_uri();
               break;
         }
      }
      //保存解析后的uri,并设置segements数组
      $this->_set_uri_string($uri);
   }

   log_message('info', 'URI Class Initialized');
}

REQUEST_URI配置的解析_prase_request_uri

 protected function _parse_request_uri()
{
   if ( ! isset($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']))

   {
      return '';
   }
   //如果不提供host,函数parse_url()会返回false
   //从凭借的uri中获取get请求参数$query和请求的路径$url
   $uri = parse_url('[http://dummy](http://dummy/)'.$_SERVER['REQUEST_URI']);
   $query = isset($uri['query']) ? $uri['query'] : '';
   $uri = isset($uri['path']) ? $uri['path'] : '';
   //去掉$uri中包含的$_SERVER['SCRIPT_NAME']
   //Q:当前的uri已经是取path得到的不应该再包含脚本名称,应该是为了防止恶意的URL引起的解析错误
   if (isset($_SERVER['SCRIPT_NAME'][0]))
   {
      if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0)
      {
         $uri = (string) substr($uri, strlen($_SERVER['SCRIPT_NAME']));
      }
      elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
      {
         $uri = (string) substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
      }
   }
   //对于服务器请求的uri的处理,保证再服务器下也能正确解析uri
   // 并且修复了QUERY_STRING和$_GET的值.
   if (trim($uri, '/') === '' && strncmp($query, '/', 1) === 0)
   {
      $query = explode('?', $query, 2);
      $uri = $query[0];
      $_SERVER['QUERY_STRING'] = isset($query[1]) ? $query[1] : '';
   }
   else
   {
      $_SERVER['QUERY_STRING'] = $query;
   }
   //将字符串解析为多个变量存入$_GET中
   parse_str($_SERVER['QUERY_STRING'], $_GET);
   if ($uri === '/' OR $uri === '')
   {
      return '/';
   }
   //去除相对路径"../"和多余的斜线"///"并返回
   return $this->_remove_relative_directory($uri);
}

QUERY_STRING配置的解析

protected function _parse_query_string()
{
   $uri = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
   //如果去掉斜线之后$uri为空返回空串
   if (trim($uri, '/') === '')
   {
      return '';
   }
   elseif (strncmp($uri, '/', 1) === 0)
   {
       //根据?分割$uri并修复$_SERVER['QUERY_STRING']的值
      $uri = explode('?', $uri, 2);
      $_SERVER['QUERY_STRING'] = isset($uri[1]) ? $uri[1] : '';
      $uri = $uri[0];
   }
   parse_str($_SERVER['QUERY_STRING'], $_GET);
       //去除相对路径"../"和多个斜线"///",然后返回
   return $this->_remove_relative_directory($uri);
}

设置uri_string的值_set_uri_string($str)

protected function _set_uri_string($str)
{
   //将处理过的$str经过去除不可见字符和trim之后保存在属性$uri_string中
   $this->uri_string = trim(remove_invisible_characters($str, FALSE), '/');

   if ($this->uri_string !== '')
   {
      //如果定义了url后缀且当前存在就移除后缀
      if (($suffix = (string) $this->config->item('url_suffix')) !== '')
      {
         $slen = strlen($suffix);

         if (substr($this->uri_string, -$slen) === $suffix)
         {
            $this->uri_string = substr($this->uri_string, 0, -$slen);
         }
      }

      $this->segments[0] = NULL;
      //以"/"分割url_string用来填充segments数组
      foreach (explode('/', trim($this->uri_string, '/')) as $val)
      {
         $val = trim($val);
         //安全处理,过滤$val
         $this->filter_uri($val);

         if ($val !== '')
         {
            $this->segments[] = $val;
         }
      }
           //由于这一步,实际有用的数据从segments的下标1开始
      unset($this->segments[0]);
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容