牛骨文教育服务平台(让学习变的简单)
<?php  if ( ! defined("BASEPATH")) exit("No direct script access allowed");
/**
 * CodeIgniter
 *
 * An open source application development framework for PHP 5.1.6 or newer
 *
 * @package		CodeIgniter
 * @author		ExpressionEngine Dev Team
 * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
 * @license		http://codeigniter.com/user_guide/license.html
 * @link		http://codeigniter.com
 * @since		Version 1.0
 * @filesource
 */

// ------------------------------------------------------------------------

/**
 * Router Class
 *
 * Parses URIs and determines routing
 *
 * @package		CodeIgniter
 * @subpackage	Libraries
 * @author		ExpressionEngine Dev Team
 * @category	Libraries
 * @link		http://codeigniter.com/user_guide/general/routing.html
 */
class CI_Router {

	/**
	 * Config class
	 * 配置
	 * @var object
	 * @access public
	 */
	var $config;
	/**
	 * List of routes
	 * 路由列表,值来自APPPATH/config/route.php
	 * @var array
	 * @access public
	 */
	var $routes			= array();
	/**
	 * List of error routes
	 * 错误路由列表
	 * @var array
	 * @access public
	 */
	var $error_routes	= array();
	/**
	 * Current class name
	 * URI中的Controller
	 * @var string
	 * @access public
	 */
	var $class			= "";
	/**
	 * Current method name
	 * URI中显示调用的函数,默认为index()
	 * @var string
	 * @access public
	 */
	var $method			= "index";
	/**
	 * Sub-directory that contains the requested controller class
	 * URI中现实的目录信息
	 * @var string
	 * @access public
	 */
	var $directory		= "";
	/**
	 * Default controller (and method if specific 确定的)
	 * 默认控制器
	 * @var string
	 * @access public
	 */
	var $default_controller;

	/**
	 * Constructor
	 *
	 * Runs the route mapping function.
	 * 加载并实例化config类和URI类
	 */
	function __construct()
	{
		$this->config =& load_class("Config", "core");
		$this->uri =& load_class("URI", "core");
		log_message("debug", "Router Class Initialized");
	}

	// --------------------------------------------------------------------

	/**
	 * Set the route mapping
	 *
	 * This function determines 确定,决心 what should be served based on the URI request,
	 * as well as any "routes" that have been set in the routing config file.
	 * 设置默认的路由信息,如果不存在控制器信息,则根据routes.php的设置来加载默认的控制器,
	 *
	 * @access	private
	 * @return	void
	 */
	function _set_routing()
	{
		// Are query strings enabled in the config file?  Normally CI doesn"t utilize 运用 query strings
		// since URI segments are more search-engine friendly, but they can optionally 视情况 be used.
		// If this feature is enabled, we will gather  the directory/class/method a little differently

		// 如果项目是允许通过query_strings的形式,并且有通过$_GET的方式请求控制器的话,则以query_string形式路由
		// 上面这里为什么还要判断有没有通过get的方式指定控制器?
		// 其实是因为如果允许query_string的形式请求路由,但是却没有在APPPATH/config/config.php下配置 
		// controller_trigger,function_trigger,directory_trigger这三项的话,也是不能使用query_strings形式的
		// 此时,我们依然会采用“段”的形式。
		
		$segments = array();
		if ($this->config->item("enable_query_strings") === TRUE AND isset($_GET[$this->config->item("controller_trigger")]))
		{
			//取得目录名,控制名和方法名传递的变量名。这三项都是在config/config.php里面定义的。
			if (isset($_GET[$this->config->item("directory_trigger")]))
			{
				$this->set_directory(trim($this->uri->_filter_uri($_GET[$this->config->item("directory_trigger")])));
				$segments[] = $this->fetch_directory();
			}

			if (isset($_GET[$this->config->item("controller_trigger")]))
			{
				$this->set_class(trim($this->uri->_filter_uri($_GET[$this->config->item("controller_trigger")])));
				$segments[] = $this->fetch_class();
			}

			if (isset($_GET[$this->config->item("function_trigger")]))
			{
				$this->set_method(trim($this->uri->_filter_uri($_GET[$this->config->item("function_trigger")])));
				$segments[] = $this->fetch_method();
			}
		}

		// Load the routes.php file.
		// 根据当前环境加载APPPATH下面的routes.php
		if (defined("ENVIRONMENT") AND is_file(APPPATH."config/".ENVIRONMENT."/routes.php"))
		{
			include(APPPATH."config/".ENVIRONMENT."/routes.php");
		}
		elseif (is_file(APPPATH."config/routes.php"))
		{
			include(APPPATH."config/routes.php");
		}
		// 下面的这个$route变量是在routes.php中定义的用来设置默认的控制器和默认的404页面
		$this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route;
		unset($route);// 利用完就干掉,过河拆桥,毫不留情。

		// Set the default controller so we can display it in the event事件
		// the URI doesn"t correlated 相关的 to a valid 有效的 controller.
		// 根据刚才的配置信息,设定默认控制器,没有的话,就为FLASE。
		$this->default_controller = ( ! isset($this->routes["default_controller"]) OR $this->routes["default_controller"] == "") ? FALSE : strtolower($this->routes["default_controller"]);

		// Were there any query string segments?  If so, we"ll validate them and bail out since we"re done.
		// 验证有没有通过query string的方式拿到目录名,控制名和方法名其中的任何一个。
		// 如果拿到了就直接确定路由返回
		if (count($segments) > 0)
		{
			// 确定并设置路由。
			return $this->_validate_request($segments);
		}

		// Fetch the complete URI string
		// 这个函数的作用是从uri中检测处理,把我们确定路由需要的信息(例如index.php/index/welcome/1后
		// 面index/welcome/1这串)放到$this->uri->uri_string中。
		$this->uri->_fetch_uri_string();

		// Is there a URI string? If not, the default controller specified in the "routes" file will be shown.
		// 如果uri_string为空的话,那么就用把路由设置为默认的。
		/*if ($this->uri->uri_string == "")
		{
			return $this->_set_default_controller();
		}*/

		// Do we need to remove the URL suffix?
		// 去掉URI后缀,因为CI允许在uri后面加后缀,但它其实对我们寻找路由是多余,而且会造成影响的,所以先去掉。
		$this->uri->_remove_url_suffix();

		// Compile 编译 the segments into an array
		// 将uri拆分正段同时对每个段进行过滤,并存入$this->segments[]中
		$this->uri->_explode_segments();

		// Parse any custom routing that may exist
		// 处理路由,根据路由设置APPPATH/config/routes.php来
		$this->_parse_routes();

		// Re-index the segment array so that it starts with 1 rather than 0
		// 将uri段索引设置为从1开始
		$this->uri->_reindex_segments();
	}

	// --------------------------------------------------------------------

	/**
	 * Set the default controller
	 * 设置默认的控制器
	 * @access	private
	 * @return	void
	 */
	function _set_default_controller()
	{
		// 在Router::_set_routing()函数中从配置文件里面读取默认控制器名,如果没有就有FALSE
		// 本文件的158行
		if ($this->default_controller === FALSE)
		{
			show_error("Unable to determine what should be displayed. A default route has not been specified in the routing file.");
		}
		// Is the method being specified?
		// 通过判断$this->default_controller有没有/来确定是否有指定的方法
		// 如果没有设置为默认方法index
		if (strpos($this->default_controller, "/") !== FALSE)
		{
			$x = explode("/", $this->default_controller);

			$this->set_class($x[0]);
			$this->set_method($x[1]);
			$this->_set_request($x);
		}
		else
		{
			$this->set_class($this->default_controller);
			$this->set_method("index");
			$this->_set_request(array($this->default_controller, "index"));
		}

		// re-index the routed segments array so it starts with 1 rather than 0
		// 重新索引段,使得出来的段以下标1开始保存。 
		$this->uri->_reindex_segments();

		log_message("debug", "No URI present. Default controller set.");
	}

	// --------------------------------------------------------------------

	/**
	 * Set the Route
	 * 设置路由
	 * This function takes 取走,预备动作 an array of URI segments as
	 * input, and sets the current class/method
	 *
	 * @access	private
	 * @param	array
	 * @param	bool
	 * @return	void
	 */
	function _set_request($segments = array())
	{
		// Router::_validate_request()的作用是检测寻找出一个正确存在的路由,并确定它
		$segments = $this->_validate_request($segments);

		//  这个函数是由_set_default_controller总调用的看216-230行会发现这个$segments 是肯定不会为空的
		//  那么下面这两句可能是为了防止本方法在其他的地方调用当参数为空的时候调用_set_default_controller()
		//  再调用回来$segments就不会为空了,因为当$segments为空的时候是不可能进行设置路由的。
		if (count($segments) == 0)
		{
			return $this->_set_default_controller();
		}
		
		// 设置类名也就是目录名
		$this->set_class($segments[0]);

		// 如果方法名存在则设置如果不存在则设置为index
		if (isset($segments[1]))
		{
			// A standard method request
			$this->set_method($segments[1]);
		}
		else
		{
			// This lets the "routed" segment array identify that the default
			// index method is being used.
			$segments[1] = "index";
		}

		// Update our "routed" segment array to contain the segments.
		// Note: If there is no custom routing, this array will be
		// identical to $this->uri->segments
		// 更新路由的段数组,这里如果没有自定义的路由那么将和$this->uri->segments是一样的
		$this->uri->rsegments = $segments;
	}

	// --------------------------------------------------------------------

	/**
	 * Validates 使有效 the supplied segments.  Attempts 企图 to determine 决定 the path to
	 * the controller.
	 * 使提供的段有效并企图决定控制器的路径
	 * @access	private
	 * @param	array
	 * @return	array
	 */
	function _validate_request($segments)
	{
		// ??????????????
		if (count($segments) == 0)
		{
			return $segments;
		}

		// Does the requested controller exist in the root folder?
		// 确定存在$segments[0]是否存在于APPPATH/controllers/文件夹下的php文件
		if (file_exists(APPPATH."controllers/".$segments[0].".php"))
		{
			return $segments;
		}

		// Is the controller in a sub-folder?
		// $segments[0]是否为APPPATH/controllers下的子目录
		if (is_dir(APPPATH."controllers/".$segments[0]))
		{
			// Set the directory and remove it from the segment array
			// 如果的确是目录,那么就可以确定路由的目录部分了。 设置目录
			$this->set_directory($segments[0]);
			// 去掉目录部分。进一步进行路由寻找。
			$segments = array_slice($segments, 1);
			
			// 如果uri请求中除了目录还有其它段,那说明是有请求某指定控制器的。
			if (count($segments) > 0)
			{
				// Does the requested controller exist in the sub-folder?
				// 判断请求的$segments[0]是不是在于APPPATH/controllers/的子目录中php文件
				if ( ! file_exists(APPPATH."controllers/".$this->fetch_directory().$segments[0].".php"))
				{
					// 报错也有两方式,一种是默认的,一种是自义定的。
					// 下面这个404_override就是在config/routes.php定义的一个路由找不
					// 到时候的默认处理控制器了,如果有定义我们调用它。
					if ( ! empty($this->routes["404_override"]))
					{
						
						$x = explode("/", $this->routes["404_override"]);

						// 把刚才设置好的路由的目录部分去掉,因为现在路由是我们定义的404路由。
						$this->set_directory("");
						// 这里可以看出,我们定义的404路由是不允许放在某个目录下的,只能直接放在controllers/下
						$this->set_class($x[0]);
						$this->set_method(isset($x[1]) ? $x[1] : "index");

						return $x;
					}
					else
					{
						// 默认404报错
						show_404($this->fetch_directory().$segments[0]);
					}
				}
			}
			else
			{
				// 如果uri请求中只有目录我们才会来到这
				// Is the method being specified 指定的 in the route?
				// 下面这个判断只是判断一下$this->default_controller有没有指定方法而已。
				if (strpos($this->default_controller, "/") !== FALSE)
				{
					$x = explode("/", $this->default_controller);

					$this->set_class($x[0]);
					$this->set_method($x[1]);
				}
				else
				{
					$this->set_class($this->default_controller);
					$this->set_method("index");
				}

				// Does the default controller exist in the sub-folder?
				// 判断APPPATH/controllers/目录/下面是否存在默认的方法
				if ( ! file_exists(APPPATH."controllers/".$this->fetch_directory().$this->default_controller.".php"))
				{
					$this->directory = "";
					return array();
				}

			}

			return $segments;
		}

		// If we"ve gotten 达到 this far 遥远的 it means that the URI does not correlate 使相关联 to a valid
		// controller class.  We will now see if there is an override
		// 来到这里,就说明了,即找不到controllers/下相应的控制器,也找不到这样的目录。那就报错咯。
		if ( ! empty($this->routes["404_override"]))
		{
			$x = explode("/", $this->routes["404_override"]);

			$this->set_class($x[0]);
			$this->set_method(isset($x[1]) ? $x[1] : "index");

			return $x;
		}

		// Nothing else to do at this point but show a 404
		// 展示一个404页面
		show_404($segments[0]);
	}

	// --------------------------------------------------------------------

	/**
	 *  Parse Routes
	 *  解析路由
	 * This function matches 匹配 any routes that may exist in
	 * the config/routes.php file against 针对,反对 the URI to
	 * determine 决定,决心 if the class/method need to be remapped.重新绘制地图
	 *
	 * @access	private
	 * @return	void
	 */
	function _parse_routes()
	{
		// Turn the segment array into a URI string
		// 将segments数组转为uri字符串的形式
		$uri = implode("/", $this->uri->segments);

		// Is there a literal 文字的,字面的 match?  If so we"re done
		// 如果这个uri是routes.php中定义的那么。。。。。
		if (isset($this->routes[$uri]))
		{
			return $this->_set_request(explode("/", $this->routes[$uri]));
		}
		// Loop through the route array looking for wild-cards
		foreach ($this->routes as $key => $val)
		{
			// Convert wild-cards to RegEx 使用通配符进行正则转换
			// 如果$key 中含有:any 转换为.+  , :num 转换为 [0-9]+
			$key = str_replace(":any", ".+", str_replace(":num", "[0-9]+", $key));
			// Does the RegEx match? 进行正则匹配
			// 从这里可以看出如果routes.php 中的$route的key是可以使用:any 或者 :num
			// 来进行匹配的。举个例子来说
			// routes.php 中有 $route["show:any:num"] = "anynum"; 
			// 这样的两个配置那么我们就可以将showaa123这样的uri匹配到了它对应的值就是anynum
			if (preg_match("#^".$key."$#", $uri))
			{
				
				// Do we have a back-reference?如果$val中有$并且$key中有(
				// 这个if的作用我没看懂。。。待高人解救
				// 有明白的请发邮箱uuus007@gmail.com
				if (strpos($val, "$") !== FALSE AND strpos($key, "(") !== FALSE)
				{
					// 将uri中能匹配到得字符替换成$val
					$val = preg_replace("#^".$key."$#", $val, $uri);
				}
				return $this->_set_request(explode("/", $val));
			}
		}

		// If we got this far it means we didn"t encounter a
		// matching route so we"ll set the site default route
		// 如果我们走到这一步,这意味着我们没有遇到一个匹配的路由
		// 所以我们将设置网站默认路由
		$this->_set_request($this->uri->segments);
	}

	// --------------------------------------------------------------------

	/**
	 * Set the class name
	 *
	 * @access	public
	 * @param	string
	 * @return	void
	 */
	function set_class($class)
	{
		$this->class = str_replace(array("/", "."), "", $class);
	}

	// --------------------------------------------------------------------

	/**
	 * Fetch the current class
	 *
	 * @access	public
	 * @return	string
	 */
	function fetch_class()
	{
		return $this->class;
	}

	// --------------------------------------------------------------------

	/**
	 *  Set the method name
	 *
	 * @access	public
	 * @param	string
	 * @return	void
	 */
	function set_method($method)
	{
		$this->method = $method;
	}

	// --------------------------------------------------------------------

	/**
	 *  Fetch the current method
	 *
	 * @access	public
	 * @return	string
	 */
	function fetch_method()
	{
		if ($this->method == $this->fetch_class())
		{
			return "index";
		}

		return $this->method;
	}

	// --------------------------------------------------------------------

	/**
	 *  Set the directory name
	 *
	 * @access	public
	 * @param	string
	 * @return	void
	 */
	function set_directory($dir)
	{
		$this->directory = str_replace(array("/", "."), "", $dir)."/";
	}

	// --------------------------------------------------------------------

	/**
	 *  Fetch the sub-directory (if any) that contains the requested controller class
	 *
	 * @access	public
	 * @return	string
	 */
	function fetch_directory()
	{
		return $this->directory;
	}

	// --------------------------------------------------------------------

	/**
	 * Set the controller overrides
	 * 控制器覆盖
	 * 这个函数可以讲目录,控制器,方法重新覆盖一遍。
	 * @access	public
	 * @param	array
	 * @return	null
	 */
	function _set_overrides($routing)
	{
		if ( ! is_array($routing))
		{
			return;
		}

		if (isset($routing["directory"]))
		{
			$this->set_directory($routing["directory"]);
		}

		if (isset($routing["controller"]) AND $routing["controller"] != "")
		{
			$this->set_class($routing["controller"]);
		}

		if (isset($routing["function"]))
		{
			$routing["function"] = ($routing["function"] == "") ? "index" : $routing["function"];
			$this->set_method($routing["function"]);
		}
	}

}
// END Router Class

/* End of file Router.php */
/* Location: ./system/core/Router.php */