• CodeIgniter URI.php 类源码解读

    URL分析的两个函数
    服务器君一共花费 19.533 ms 进行了 3 次数据库查询,努力地为您提供了这个页面。
    广告很萌的

    前面介绍了 index.php,也就是 CodeIgniter 框架的启动顺序。也许咋一看似乎很复杂,其实理解了也就没什么了。完成框架的一次请求处理,也就几个文件,下面是我自己的文件目录:

    接下来会慢慢介绍到这些类,核心也就是这么几个文件而已。

    URI.php

    在 index.php 中,有这么几行代码:

    // 自动加载所需的类 
    require('core/Common.php');
    // 加载 core/URI.php 类
    $URI =& load_class('URI');
    

    然后就打印 $URI->rsegments 这个东西的结果是:

    Array
    (
        [0] => welcome
        [1] => nowamagic
    )
    

    这个对象的方法可以猜得到,就是分析 URL,解析出类的名与方法。

    我们先来读一下 CodeIgniter 中 URI.php 的源码。这个类最主要的是 _fetch_uri_string() 与 _detect_uri() 函数。先是 _fetch_uri_string() 函数:

    <?php
    // URI组件里面有很多方法,大都是一些辅助作用的方法,而此方法是URI最主线的一个方法。
    function _fetch_uri_string()
    {
    	//下面的uri_protocol是在config.php里面的一个配置项,其实是问你用哪种方式去检测uri的信息的意思,
        //默认是AUTO,自动检测,也就是通过各种方式检测,直至检测到,或者全部方式都检测完。
    	if (strtoupper($this->config->item('uri_protocol')) == 'AUTO')
    	{
    		// Is the request coming from the command line?
    		//开始尝试各种方式,主要有:命令行,REQUEST_URI, PATH_INFO, QUERY_STRING.
    
            //下面会多次出现$this->_set_uri_string($str)这个方法,这个方法没别的,就是把$str经过
            //过滤和修剪后值给$this->uri_string属性,在这里暂时可以理解为就是赋值。
    
            //如果脚本是在命令行模式下运行的话,那么参数就是通过$_SERVER['argv']来传递。下面的
            //$this->_parse_cli_args();就是拿到符合我们需要的路由相关的一些参数鸟~如果大部分
            //情况你没用命令行执行脚本的话,下面这个if暂时可以不用管。
    		if (php_sapi_name() == 'cli' or defined('STDIN'))
    		{
    			$this->_set_uri_string($this->_parse_cli_args());
    			return;
    		}
    
    		// Let's try the REQUEST_URI first, this will work in most situations
    		// 这种REQUEST_URI方式相对复杂一点,因此封装在$this->_detect_uri(); 里面。
            // 其实大多数情况下,利用REQUEST URI和SCRIPT NAME都会得到我们想要的路径信息了。
    		if ($uri = $this->_detect_uri())
    		{
    			$this->_set_uri_string($uri);
    			return;
    		}
    
    		// Is there a PATH_INFO variable?
    		// Note: some servers seem to have trouble with getenv() so we'll test it two ways
    		// PATH_INFO方式,个人觉得这种方式最经济,只是不是每次请求都有$_SERVER['PATH_INFO']这个变量。
    		$path = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');
    		if (trim($path, '/') != '' && $path != "/".SELF)
    		{
    			$this->_set_uri_string($path);
    			return;
    		}
    
    		// No PATH_INFO?... What about QUERY_STRING?
    		// 如果是用QUERY_STRING的话,路径格式一般为index.php?/controller/method/xxx/xxx
    		$path =  (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
    		if (trim($path, '/') != '')
    		{
    			$this->_set_uri_string($path);
    			return;
    		}
    
    		// As a last ditch effort lets try using the $_GET array
    		// 上面的方法都不行,那真是奇怪了。。所以尝试最后一种奇葩的方法,就是从$_GET里面把那个键名拿出来。
    		if (is_array($_GET) && count($_GET) == 1 && trim(key($_GET), '/') != '')
    		{
    			$this->_set_uri_string(key($_GET));
    			return;
    		}
    
    		// We've exhausted all our options...
    		$this->uri_string = '';
    		return;
    	}
    	
    	// 这里是因为上面那个获得uri_protocol配置的语句写在if里面,然后又没赋值到某个变量,所以这里要再写一次了
        // 可能是因为大多数情况下,我们都是选择AUTO方式吧。
    	$uri = strtoupper($this->config->item('uri_protocol'));
    	
    	//其实就是按规定的方式去找路径而已
    	if ($uri == 'REQUEST_URI')
    	{
    		$this->_set_uri_string($this->_detect_uri());
    		return;
    	}
    	elseif ($uri == 'CLI')
    	{
    		$this->_set_uri_string($this->_parse_cli_args());
    		return;
    	}
    	
    	//如果你在配置文件config.php里面把这个uri_protocol定义成一种上面都没有的方式,那么就会执行下面的代码。
        //意思是,就看$_SERVER有没有这个uri_protocol的变量了,有就给,没有就算了。
    	$path = (isset($_SERVER[$uri])) ? $_SERVER[$uri] : @getenv($uri);
    	$this->_set_uri_string($path);
    }
    ?>
    

    再来看看 _detect_uri() 这个函数:

    <?php
    	/**
    	 * Detects the URI
    	 *
    	 * This function will detect the URI automatically and fix the query string
    	 * if necessary.
    	 *
    	 * @access	private
    	 * @return	string
    	 */
    	private function _detect_uri()
    	{
    		//如果这两个值缺少其中一个,那么这种方法行不通
    		if ( ! isset($_SERVER['REQUEST_URI']) OR ! isset($_SERVER['SCRIPT_NAME']))
    		{
    			return '';
    		}
    		//取得request_uri
    		$uri = $_SERVER['REQUEST_URI'];
    		//注意下面这个是===0不是false! 接下来这个if和下面的elseif分别是script_name有文件名和没有文件名(如
      		//http://abc.com/CI/或者http://abc.com/CI/?c=index&m=welcome等)的不同情况的处理。
    		if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0)
    		{
    			//去掉共同部分,取得对路由有用的部分。
    			$uri = substr($uri, strlen($_SERVER['SCRIPT_NAME']));
    		}
    		elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
    		{
    			//作用同上
    			$uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
    		}
    
    		// This section ensures that even on servers that require the URI to be in the query string (Nginx) a correct
    		// URI is found, and also fixes the QUERY_STRING server var and $_GET array.
    		//这里是为兼容?/abc/xx/的形式。
    		if (strncmp($uri, '?/', 2) === 0)
    		{
    			$uri = substr($uri, 2);
    		}
    		//在这里$uri可能是?xxx=xx的形式,也可能是直接xxx=xx,也可能是/
    		$parts = preg_split('#\?#i', $uri, 2);
    		$uri = $parts[0];
    		//如果是能通过上述的正则分割出两段,那么,是通过query_string即?的形式进行路由访问
    		if (isset($parts[1]))
    		{
    			$_SERVER['QUERY_STRING'] = $parts[1];
    			parse_str($_SERVER['QUERY_STRING'], $_GET);
    		}
    		else
    		{
    			$_SERVER['QUERY_STRING'] = '';
    			$_GET = array();
    		}
    		
    		//如果为/,或者为空,有两种情况,要么就是通过query_string,所以此时$parts[0]就是等于下面两种可能,同时我们
    	  	//已经通过$parts[1]拿到要拿的信息,则可以返回。要么就是以段的形式,但是段的信息为空,即直接访问入口文件而没有
    	  	//任何路由信息的传递,也可以直接返回。
    		if ($uri == '/' || empty($uri))
    		{
    			return '/';
    		}
    		
    		//这里我个人觉得是上面的strpos($uri, $_SERVER['SCRIPT_NAME']) === 0和elseif都无法匹配的时候,
      		//返回这个url的path部分。
    		$uri = parse_url($uri, PHP_URL_PATH);
    
    		// Do some final cleaning of the URI and return it
    		return str_replace(array('//', '../'), '/', trim($uri, '/'));
    	}
    ?>
    

    我们自己写的话可以不用考虑那么多,下一小节我们自己来简化一下 URI.php 的两个函数。

更多 推荐条目

Welcome to NowaMagic Academy!

现代魔法 推荐于 2013-02-27 10:23   

本章最新发布
随机专题
  1. [移动开发] Android 开发调试工具 ADB 3 个条目
  2. [智力开发与知识管理] 整体性学习策略 9 个条目
  3. [PHP程序设计] PHP中的Hash算法 3 个条目
  4. [移动开发] Android布局中的一些常用控件 2 个条目
  5. [移动开发] Android根基概念Context 8 个条目
  6. [C语言程序设计] C语言里的全局变量 2 个条目
  7. [Linux操作系统] CentOS上使用EPEL Repository 2 个条目
  8. [PHP程序设计] PHP数组探索 4 个条目
  9. [Python程序设计] Python HTTP服务器 7 个条目
  10. [移动开发] Android View注入框架Butter Knife 3 个条目
  11. [运维管理] 防火墙原理与应用 5 个条目
  12. [移动开发] 刷机与root相关 2 个条目
窗口 -- [资讯]