2016年8月10日23:17
路由的目的是為了從URL中解析出class類名是什么,method方法名是什么,所傳的參數(shù)有哪些辖所,參數(shù)值又是什么,類文件存在的路徑是哪磨德。
最終實(shí)現(xiàn)方法的調(diào)度缘回。
CI支持基于段方法和查詢字符串方法兩種形式的URL吆视。
基于段形式:
example.com/news/article/my_article.html
查詢字符串形式:
index.php?c=products&m=view&id=345
URL的獲取方法有如下幾種:PATH_INFO、QUERY_STRING酥宴、REQUEST_URI揩环、ORIG_PATH_INFO。比較常用的是PATH_INFO幅虑。幾種方式的差異可以簡(jiǎn)單通過(guò)打印$_SERVER來(lái)查看丰滑。比如xxx.com/welcome/test_search.html?c=welcome&d=test_search,打印的結(jié)果是(只挑了這幾部分):
Array
(
[QUERY_STRING] => c=welcome&d=test_search
[REQUEST_URI] => /welcome/test_search.html?c=welcome&d=test_search
[PATH_INFO] => /welcome/test_search.html
)
下面是源碼config文件里關(guān)于這幾種方法的定義倒庵。
/*
|--------------------------------------------------------------------------
| URI PROTOCOL
|--------------------------------------------------------------------------
|
| This item determines which server global should be used to retrieve the
| URI string. The default setting of 'AUTO' works for most servers.
| If your links do not seem to work, try one of the other delicious flavors:
|
| 'AUTO' Default - auto detects
| 'PATH_INFO' Uses the PATH_INFO
| 'QUERY_STRING' Uses the QUERY_STRING
| 'REQUEST_URI' Uses the REQUEST_URI
| 'ORIG_PATH_INFO' Uses the ORIG_PATH_INFO
|
*/
$config['uri_protocol'] = 'PATH_INFO';
我們這里用的也是PATH_INFO來(lái)獲取褒墨。
至此,我們就擁有了URL地址擎宝,接下來(lái)我們就要分析地址郁妈。URL類就是來(lái)做分析的事情。
system/core/Router.php里的_set_routing()方法就是利用URL類來(lái)實(shí)現(xiàn)解析出類名方法名绍申。
看下代碼(下面的代碼都是CodeIgniter-3.0.6版本的)噩咪,英文注釋已經(jīng)很詳細(xì)了,我在關(guān)鍵點(diǎn)額外加了點(diǎn)中文注釋:
/**
* Set route mapping
*
* Determines what should be served based on the URI request,
* as well as any "routes" that have been set in the routing config file.
*
* @return void
*/
protected function _set_routing()
{
// Load the routes.php file. It would be great if we could
// skip this for enable_query_strings = TRUE, but then
// default_controller would be empty ...
if (file_exists(APPPATH.'config/routes.php'))
{
include(APPPATH.'config/routes.php');
}
if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php'))
{
include(APPPATH.'config/'.ENVIRONMENT.'/routes.php');
}
// Validate & get reserved routes
if (isset($route) && is_array($route))
{
isset($route['default_controller']) && $this->default_controller = $route['default_controller'];
isset($route['translate_uri_dashes']) && $this->translate_uri_dashes = $route['translate_uri_dashes'];
unset($route['default_controller'], $route['translate_uri_dashes']);
$this->routes = $route;
}
// 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
// 這段不用看极阅,我們項(xiàng)目中是FALSE
if ($this->enable_query_strings)
{
// If the directory is set at this time, it means an override exists, so skip the checks
if ( ! isset($this->directory))
{
$_d = $this->config->item('directory_trigger');
$_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : '';
if ($_d !== '')
{
$this->uri->filter_uri($_d);
$this->set_directory($_d);
}
}
$_c = trim($this->config->item('controller_trigger'));
if ( ! empty($_GET[$_c]))
{
$this->uri->filter_uri($_GET[$_c]);
$this->set_class($_GET[$_c]);
$_f = trim($this->config->item('function_trigger'));
if ( ! empty($_GET[$_f]))
{
$this->uri->filter_uri($_GET[$_f]);
$this->set_method($_GET[$_f]);
}
$this->uri->rsegments = array(
1 => $this->class,
2 => $this->method
);
}
else
{
$this->_set_default_controller();
}
// Routing rules don't apply to query strings and we don't need to detect
// directories, so we're done here
return;
}
// Is there anything to parse?
//這里的$this->uri是route類構(gòu)造函數(shù)里面$this->uri =& load_class('URI', 'core');來(lái)的胃碾,后文我們會(huì)詳細(xì)看URI類
if ($this->uri->uri_string !== '')
{
//絕大多數(shù)情況是會(huì)走這里
$this->_parse_routes();
}
else
{
$this->_set_default_controller();
}
所以我們重點(diǎn)再看ROUTE的_parse_routes()方法:
/**
* Parse Routes
*
* 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.
*
* @return void
*/
protected function _parse_routes()
{
// Turn the segment array into a URI string
// $this->uri->segments我們后面也會(huì)講到
$uri = implode('/', $this->uri->segments);
// Get HTTP verb
$http_verb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli';
// Loop through the route array looking for wildcards
// 查看是否符合config文件里的配置
foreach ($this->routes as $key => $val)
{
// Check if route format is using HTTP verbs
if (is_array($val))
{
$val = array_change_key_case($val, CASE_LOWER);
if (isset($val[$http_verb]))
{
$val = $val[$http_verb];
}
else
{
continue;
}
}
// Convert wildcards to RegEx
$key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key);
// Does the RegEx match?
if (preg_match('#^'.$key.'$#', $uri, $matches))
{
// Are we using callbacks to process back-references?
if ( ! is_string($val) && is_callable($val))
{
// Remove the original string from the matches array.
array_shift($matches);
// Execute the callback using the values in matches as its parameters.
$val = call_user_func_array($val, $matches);
}
// Are we using the default routing method for back-references?
elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE)
{
$val = preg_replace('#^'.$key.'$#', $val, $uri);
}
$this->_set_request(explode('/', $val));
return;
}
}
// 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(array_values($this->uri->segments));
}
再看_set_request()方法:
/**
* Set request route
*
* Takes an array of URI segments as input and sets the class/method
* to be called.
*
* @used-by CI_Router::_parse_routes()
* @param array $segments URI segments
* @return void
*/
protected function _set_request($segments = array())
{
$segments = $this->_validate_request($segments);
// If we don't have any segments left - try the default controller;
// WARNING: Directories get shifted out of the segments array!
if (empty($segments))
{
$this->_set_default_controller();
return;
}
if ($this->translate_uri_dashes === TRUE)
{
$segments[0] = str_replace('-', '_', $segments[0]);
if (isset($segments[1]))
{
$segments[1] = str_replace('-', '_', $segments[1]);
}
}
//找到類名了
$this->set_class($segments[0]);
if (isset($segments[1]))
{
//找到方法名了
$this->set_method($segments[1]);
}
else
{
$segments[1] = 'index';
}
array_unshift($segments, NULL);
unset($segments[0]);
$this->uri->rsegments = $segments;
到這里,就根據(jù)URL找出了類名和方法名筋搏,在引導(dǎo)文件system/core/CodeIgniter.php里面就可以實(shí)現(xiàn)調(diào)度了仆百。
$class = ucfirst($RTR->class);
$method = $RTR->method;
$CI = new $class();
/*
* ------------------------------------------------------
* Call the requested method
* ------------------------------------------------------
*/
call_user_func_array(array(&$CI, $method), $params);
下面我們看下URI類,來(lái)看看上面代碼里的$this->uri等各項(xiàng)是怎么得到的奔脐。
在URI類的構(gòu)造函數(shù)里有一步:
$this->_set_uri_string($uri)
_set_uri_string()方法代碼如下:
/**
* Set URI String
*
* @param string $str
* @return void
*/
protected function _set_uri_string($str)
{
// Filter out control characters and trim slashes
$this->uri_string = trim(remove_invisible_characters($str, FALSE), '/');
if ($this->uri_string !== '')
{
// Remove the URL suffix, if present
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;
// Populate the segments array
foreach (explode('/', trim($this->uri_string, '/')) as $val)
{
$val = trim($val);
// Filter segments for security
$this->filter_uri($val);
if ($val !== '')
{
$this->segments[] = $val;
}
}
unset($this->segments[0]);
}
}
如果你的原始uri是http://example.com/index.php/news/local/metro/crime_is_up的話俄周,經(jīng)過(guò)這個(gè)_set_uri_string()方法處理,你會(huì)得到$this->segments = array([1]=>'news', [2]=>'local',[3]=>'metro',[4]=>'crime_is_up')