路由的目的是爲了從URL中解析出class類名是什麼,method方法名是什麼,所傳的參數有哪些,參數值又是什麼,類文件存在的路徑是哪。
最終實現方法的調度。php
CI支持基於段方法和查詢字符串方法兩種形式的URL。html
基於段形式:app
example.com/news/article/my_article.html
查詢字符串形式:ide
index.php?c=products&m=view&id=345
URL的獲取方法有以下幾種:PATH_INFO、QUERY_STRING、REQUEST_URI、ORIG_PATH_INFO。比較經常使用的是PATH_INFO。幾種方式的差別能夠簡單經過打印$_SERVER來查看。好比xxx.com/welcome/test_search.html?c=welcome&d=test_search,打印的結果是(只挑了這幾部分):函數
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文件裏關於這幾種方法的定義。codeigniter
/* |-------------------------------------------------------------------------- | 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來獲取。oop
至此,咱們就擁有了URL地址,接下來咱們就要分析地址。URL類就是來作分析的事情。ui
system/core/Router.php裏的_set_routing()方法就是利用URL類來實現解析出類名方法名。this
看下代碼(下面的代碼都是CodeIgniter-3.0.6版本的),英文註釋已經很詳細了,我在關鍵點額外加了點中文註釋:url
/** * 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 // 這段不用看,咱們項目中是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類構造函數裏面$this->uri =& load_class('URI', 'core');來的,後文咱們會詳細看URI類 if ($this->uri->uri_string !== '') { //絕大多數狀況是會走這裏 $this->_parse_routes(); } else { $this->_set_default_controller(); }
因此咱們重點再看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咱們後面也會講到 $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;
到這裏,就根據URL找出了類名和方法名,在引導文件system/core/CodeIgniter.php裏面就能夠實現調度了。
$class = ucfirst($RTR->class); $method = $RTR->method; $CI = new $class(); /* * ------------------------------------------------------ * Call the requested method * ------------------------------------------------------ */ call_user_func_array(array(&$CI, $method), $params);
下面咱們看下URI類,來看看上面代碼裏的$this->uri等各項是怎麼獲得的。
在URI類的構造函數裏有一步:
$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的話,通過這個_set_uri_string()方法處理,你會獲得$this->segments = array([1]=>'news', [2]=>'local',[3]=>'metro',[4]=>'crime_is_up')