CI框架源碼閱讀---------Router.php

<?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的作用我沒看懂。。。待高人解救
				// 有明白的請發郵箱[email protected]
				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 */

發佈了80 篇原創文章 · 獲贊 18 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章