CI框架源碼解讀--ROUTE和URL類

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')

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末髓迎,一起剝皮案震驚了整個(gè)濱河市峦朗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌排龄,老刑警劉巖波势,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異涣雕,居然都是意外死亡艰亮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門挣郭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)迄埃,“玉大人,你說(shuō)我怎么就攤上這事兑障≈斗牵” “怎么了蕉汪?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)逞怨。 經(jīng)常有香客問(wèn)我者疤,道長(zhǎng),這世上最難降的妖魔是什么叠赦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任驹马,我火速辦了婚禮,結(jié)果婚禮上除秀,老公的妹妹穿的比我還像新娘糯累。我一直安慰自己,他們只是感情好册踩,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布泳姐。 她就那樣靜靜地躺著,像睡著了一般暂吉。 火紅的嫁衣襯著肌膚如雪胖秒。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天慕的,我揣著相機(jī)與錄音阎肝,去河邊找鬼。 笑死业稼,一個(gè)胖子當(dāng)著我的面吹牛盗痒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播低散,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼骡楼!你這毒婦竟也來(lái)了熔号?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸟整,失蹤者是張志新(化名)和其女友劉穎引镊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體篮条,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弟头,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涉茧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赴恨。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖伴栓,靈堂內(nèi)的尸體忽然破棺而出伦连,到底是詐尸還是另有隱情雨饺,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布惑淳,位于F島的核電站额港,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏歧焦。R本人自食惡果不足惜移斩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绢馍。 院中可真熱鬧向瓷,春花似錦、人聲如沸痕貌。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)舵稠。三九已至超升,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哺徊,已是汗流浹背室琢。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留落追,地道東北人盈滴。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像轿钠,于是被迫代替她去往敵國(guó)和親巢钓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容