天天看點

詳解thinkphp的U函數及其源碼分析

最近太忙了,好長一段時間沒寫部落格,這段時間總算稍微有點時間了,準備看看thinkphp的源碼,已經看了一些,但離看完還差得遠,總之先單獨記錄一下看過的源碼,以後的東西在陸續放出。

注:我的看的版本不是最新的thinkphp,是3.23的版本。

基礎性的講解:

U函數有四個參數,如 U($url='',$vars='',$suffix=true,$domain=false):

$url是要解析的url的表達式,格式是[子產品/控制器/操作#錨點@域名]?參數1=值1&參數2=值2...],可以定義路由,直接在最前面加上/即可

$vars是url的參數值對,string|array類型,字元串形式格式aaa=1&bbb=2

$suffix是url是否帶字尾,這個參數是string|boolean類型,true是帶字尾,false則為不帶,預設為true,也可以直接填寫字尾的,例如.html。

$domain是url是否帶域名,true顯示域名 $url有@域名部分則顯示這部分的域名,如果沒有則根據情況擷取,false不顯示域名,false要起效,url保證沒有@域名部分,否則還是會顯示@域名部分的域名,因為@域名最後解析會指派給$domain,這樣$domain設定為false就無效了,會變成true

U函數源碼,源碼解釋盡可能詳細了,可以配合下面的流程圖來了解,這裡面還涉及了操作、控制器、子產品映射,由于篇幅問題,以後單獨拿出來寫一篇,下面注釋也有解釋:

/**
 * URL組裝 支援不同URL模式
 * @param string $url URL表達式,格式:'[子產品/控制器/操作#錨點@域名]?參數1=值1&參數2=值2...'
 * @param string|array $vars 傳入的參數,支援數組和字元串
 * @param string|boolean $suffix 僞靜态字尾,預設為true表示擷取配置值,false或空表示沒有字尾,還可以直接填寫字尾,例如.html
 * @param boolean $domain 是否顯示域名
 *                        true顯示域名 $url有@域名部分則顯示這部分的域名,如果沒有則根據情況擷取
 *                        false不顯示域名,false要起效,url保證沒有@域名部分,否則還是會顯示@域名部分的域名
 *                        因為@域名最後解析會指派給$domain,這樣$domain設定為false就無效了,會變成true                      
 * @return string
 */
function U($url='',$vars='',$suffix=true,$domain=false) {
    // 解析URL
    /*parse_url解析url表達式成數組形式并指派給$info,根據url表達式會分為path和fragment兩部分
    path對應子產品/控制器/操作,fragment對應#錨點@域名?參數1=值1&參數2=值2...*/
    $info   =  parse_url($url);

    /*檢查$info['path']是否為空,如果不為空,則将其指派給$url,如果為空,則将操作名指派給$url*/
    $url    =  !empty($info['path'])?$info['path']:ACTION_NAME;

    /*檢查$info['fragment']是否設定了*/
    if(isset($info['fragment'])) { // 解析錨點
        /*如果設定了,則将其指派給$anchor*/
        $anchor =   $info['fragment'];

        /*檢查$anchor中是否有?,且?不是首字元,注意?後是跟query的字元串,這裡跟随的是1個或多個參數對*/
        if(false !== strpos($anchor,'?')) { // 解析參數
            /*如果有則根據?将$anchor打散成兩個單元值的數組,?号前對應的單元值指派給$anchor,
            ?後對應的單元值指派$info['query']*/
            list($anchor,$info['query']) = explode('?',$anchor,2);
        }      

        /*檢查$anchor裡是否存在@,且不是首字元,注意@後是跟域名的字元串*/
        if(false !== strpos($anchor,'@')) { // 解析域名
            /*根據@将其分割成兩個單元的數組,@前對應的數組單元值指派給$anchor,@後對應的單元值指派給$host*/
            list($anchor,$host)    =   explode('@',$anchor, 2);
        }
        
    /*如果沒有設定$info['fragment'],則檢查$url裡是否存在@,且不為首字元*/
    }elseif(false !== strpos($url,'@')) { // 解析域名
        /*根據@分割成兩個單元的數組,然後将第一個單元也就是@前的部分指派給$url,
        @後面對應的單元值指派給$url*/
        list($url,$host)    =   explode('@',$info['path'], 2);
    }


    // 解析子域名
    /*檢查$host是否設定*/
    if(isset($host)) {
        /*如果設定了,檢查$host是否存在.,如果存在且不位于首字元,則傳回空,如果不存在或者.為首字元,
        則檢查$_SERVER['HTTP_HOST']裡是否存在.,如果存在.并傳回第一個出現的位置到最後結束部分的字元,
        将傳回的值拼接到$host後組成域名$domain

        例如$host=mq,$_SERVER['HTTP_HOST']=www.baidu.com,這裡得到的是
        mq.baidu.com

        例如$host=.mq 會拼接成 .mq.baidu.com*/
        $domain = $host.(strpos($host,'.')?'':strstr($_SERVER['HTTP_HOST'],'.'));

    /*如果沒有設定$host,則檢查使用者送出$domain是否恒等于true*/
    }elseif($domain===true){
        /*如果帶域名,則擷取$_SERVER['HTTP_HOST']的值指派給$domain*/
        $domain = $_SERVER['HTTP_HOST'];

        /*檢查是否開啟了子域名配置*/
        if(C('APP_SUB_DOMAIN_DEPLOY') ) { // 開啟子域名部署
            /*如果開啟了,則檢查$domain是否恒等于localhost,如果是,則傳回locahost,如果不是,
            則用www拼接$_SERVER['HTTP_HOST']的第一個.開始位置到結束部分的字元串*/
            $domain = $domain=='localhost'?'localhost':'www'.strstr($_SERVER['HTTP_HOST'],'.');
            // '子域名'=>array('子產品[/控制器]');
            /*擷取配置檔案裡的APP_SUB_DOMAIN_RULES并進行循環
            循環$key是比對的條件,$rule是規則,U函數隻支援子域名比對規則的數組和字元串形式*/
            foreach (C('APP_SUB_DOMAIN_RULES') as $key => $rule) {
                /*判斷循環的規則$rule是不是數組,如果是則将$rule[0]作為規則$rule
                如果不是數組,則将其直接作為$rule*/
                $rule   =   is_array($rule)?$rule[0]:$rule;

                /*檢查$key是否存在*号,如果不存在同時$url包含$rule的比對規則,并是從首字元開始比對的*/
                if(false === strpos($key,'*') && 0=== strpos($url,$rule)) {
                    /*如果兩個條件都滿足,将域名檢查是否包含.如果包含則将其所在的位置前面拼接$key,
                    最後複制給$domain的域名*/
                    $domain = $key.strstr($domain,'.'); // 生成對應子域名
                    /*将$url的第0個字元開始,長度為$rule長度的部分替換成空值*/
                    $url    =  substr_replace($url,'',0,strlen($rule));
                    /*跳出循環*/
                    break;
                }
            }
        }
    }

    // 解析參數
    /*檢查使用者傳入的$vars的參數是不是字元串*/
    if(is_string($vars)) { // aaa=1&bbb=2 轉換成數組
        /*如果是字元串,将其url的query形式字元串解析成數組形式傳回給$vars*/
        parse_str($vars,$vars);
    /*如果不是字元串,檢查參數$vars是不是數組*/
    }elseif(!is_array($vars)){
        /*如果不是數組,也不是字元串,将其指派為空數組*/
        $vars = array();
    }

    
    /*檢查$info['query']是否設定*/
    if(isset($info['query'])) { // 解析位址裡面參數 合并到vars
        /*如果設定了,将其解析到$params數組裡*/
        parse_str($info['query'],$params);
        /*将$params和$vars數組合并後重新指派給$vars*/
        $vars = array_merge($params,$vars);
    }
    

    // URL組裝
    /*擷取url配置的分隔符并指派給$depr*/
    $depr       =   C('URL_PATHINFO_DEPR');
    /*擷取配置檔案中的url是否大小寫敏感指派給$urlCase*/
    $urlCase    =   C('URL_CASE_INSENSITIVE');
    /*檢查$url是否存在*/

    if($url) {
        /*如果存在,則檢查$url首字元是不是/*/
        if(0=== strpos($url,'/')) {// 定義路由
            /*如果是則設定$route為true*/
            $route      =   true;
            /*去掉$url的首字元/*/
            $url        =   substr($url,1);
            /*檢查url的分隔符是不是/*/
            if('/' != $depr) {
                /*如果不是,則将$url裡的/替換成設定的分隔符$depr*/
                $url    =   str_replace('/',$depr,$url);
            }
        /*如果首字元不是/,則不定義路由*/
        }else{
            /*檢查分隔符是不是/*/
            if('/' != $depr) { // 安全替換
                /*如果/不是分隔符将/替換成設定的分隔符*/
                $url    =   str_replace('/',$depr,$url);
            }

            // 解析子產品、控制器和操作
            /*修剪$url兩端的分隔符*/
            $url        =   trim($url,$depr);
            /*根據分隔符将$url分割成數組并指派給$path*/
            $path       =   explode($depr,$url);
            /*給$var初始化為空數組*/
            $var        =   array();
            /*将子產品變量名指派給$varModule,預設是m*/
            $varModule      =   C('VAR_MODULE');
            /*将控制器變量名指派給$varController,預設是c*/
            $varController  =   C('VAR_CONTROLLER');
            /*将方法變量名指派給$varAction,預設是a*/
            $varAction      =   C('VAR_ACTION');

            /*檢查$path是否為空,如果不為空,則将其取出數組裡的最後一個單元值指派給$var[$varAction],
            如果為空,則調用常量ACTION_NAME作為指派的值*/
            $var[$varAction]       =   !empty($path)?array_pop($path):ACTION_NAME;
            /*檢查$path是否為空,如果不為空,再次取出最後一個單元值指派給$var[$varController],
            如果為空,則調用常量CONTROLLER_NAME*/
            $var[$varController]   =   !empty($path)?array_pop($path):CONTROLLER_NAME;


            /*擷取URL_ACTION_MAP操作映射配置并指派給$maps,并檢查是否為空
            這裡用來檢查是否開啟了操作映射并根據映射進行替換*/
            if($maps = C('URL_ACTION_MAP')) {

                /*如果不為空則将$var[$varController]轉變為小寫,并作為$maps的鍵檢查該值是否設定
                這裡是為了操作映射裡找到對應控制器對應的操作映射*/
                if(isset($maps[strtolower($var[$varController])])) {

                    /*如果存在,将其指派給$maps*/
                    $maps    =   $maps[strtolower($var[$varController])];

                    /*将$var[$varAction]轉為小寫,同時檢查該值是否存在于$maps内,如果存在傳回其值對應的鍵指派給$action
                    如果不存在,則傳回false給$action,檢測$action是否為真*/
                    if($action = array_search(strtolower($var[$varAction]),$maps)){
                        /*如果$action存在,則将$action指派給$var[$varAction]*/
                        $var[$varAction] = $action;
                    }
                }
            }

            /*檢測URL_CONTROLLER_MAP是否存在,如果存在指派給$maps,并檢測其的值是否為真*/
            if($maps = C('URL_CONTROLLER_MAP')) {
                /*如果為真,則檢測$maps是否存在$var[$varController]的小寫值,如果存在,則将其對應的鍵指派給$controller,
                如果不存在則指派false*/
                if($controller = array_search(strtolower($var[$varController]),$maps)){
                    /*将$controller指派給$var[$varController]*/
                    $var[$varController] = $controller;
                }
            }

            /*檢查$urlCase也就是url是否區分大小寫 true為不區分,false為區分*/
            if($urlCase) {
                /*如果不區分大小寫,則将$var[$varController]轉化為小寫*/
                $var[$varController]   =   parse_name($var[$varController]);
            }

            /*$module初始化為空字元串*/
            $module =   '';
            

            /*檢測$path是否為空*/
            if(!empty($path)) {
                /*如果不為空,根據分隔符将$path分割成數組,并指派給$var[$varModule]*/
                $var[$varModule]    =   implode($depr,$path);
            }else{
                /*如果為空,則需要自行判斷子產品是哪個
                檢查是否允許多子產品的配置,true為允許,false為不允許,并且需要設定預設子產品DEFAULT_MODULE*/
                if(C('MULTI_MODULE')) {
                    /*如果為true,則檢測MODULE_NAME常量是不是等于預設子產品配置,如果等于,
                    再檢測允許子產品清單名單是否為false,以下有兩種可能
                    如果通路子產品不為預設的子產品 則$var[$varModule]子產品值為通路子產品
                    如果通路子產品為預設子產品,則允許的子產品清單為false,則$var[$varModule]子產品值為通路子產品
                    */
                    if(MODULE_NAME != C('DEFAULT_MODULE') || !C('MODULE_ALLOW_LIST')){
                        /*如果子產品常量不等于預設子產品,或者沒有設定子產品允許清單,将其指派給$var[$varModule]*/
                        $var[$varModule]=   MODULE_NAME;
                    }
                }
            }

            

            /*檢查子產品映射配置是否設定,并指派給$maps,如果沒有設定就是false*/
            if($maps = C('URL_MODULE_MAP')) {
                /*如果$maps子產品映射設定了,則檢查$var[$varModule]的小寫是否存在于$maps中,如果存在将其
                指派給$_module,如果不存在指派$_module為false*/
                if($_module = array_search(strtolower($var[$varModule]),$maps)){
                    /*如果存在,将$var[$varModule]指派$_module*/
                    $var[$varModule] = $_module;
                }
            }

            /*檢查$var[$varModule]是否設定了*/
            if(isset($var[$varModule])){
                /*如果設定了,則将其指派給$module*/
                $module =   $var[$varModule];
                /*同時登出$var[$varModule]*/
                unset($var[$varModule]);
            }
            
        }
    }

    /*檢測配置URL_MODEL通路模式是否等于0模式,也就是普通URL通路模式*/
    if(C('URL_MODEL') == 0) { // 普通模式URL轉換
        /*如果是,則将url拼接起來,__APP__為項目入口檔案位址,VAR_MODULE為子產品變量名配置
        $module是上面擷取到的子產品名稱,$var是數組,裡面包含了控制器及其方法以及其他的查詢參數值,
        array_reverse将其倒轉順序後使用http_build_query拼接為url查詢語句形式
        拼接好的url指派給$url
        */
        $url        =   __APP__.'?'.C('VAR_MODULE')."={$module}&".http_build_query(array_reverse($var));
        /*檢查是否大小寫敏感,true為不敏感*/
        if($urlCase){
            /*将$url全部轉化為小寫*/
            $url    =   strtolower($url);
        }        
        /*檢查$vars是否為空*/
        if(!empty($vars)) {
            /*如果不為空,則将其轉變為url查詢語句形式*/
            $vars   =   http_build_query($vars);
            /*同時将其拼接在$url後并添加&符*/
            $url   .=   '&'.$vars;
        }
    /*如果url通路不為普通模式*/
    }else{ // PATHINFO模式或者相容URL模式
        /*檢測是否設定了$route,也就是定義了路由,定義路由的情況直接用$url定義的部分,
        而沒有定義則使用前面擷取到的$var來組裝成$url*/
        if(isset($route)) {
            /*如果定義了路由,則用__APP__項目入口檔案位址拼接/和去除$url後側url分隔符的部分并重新指派給$url*/
            $url    =   __APP__.'/'.rtrim($url,$depr);
        }else{
            /*如果沒有定義路由,則檢測是否定義BIND_MODULE的常量,同時檢測BIND_MODULE是否等于$module,如果兩個條件
            都滿足的話,$module為空值,如果不滿足,則$module不變*/
            $module =   (defined('BIND_MODULE') && BIND_MODULE==$module )? '' : $module;
            /*項目入口檔案後面拼接/和子產品名以及子產品分割符,如果子產品名不存在,則拼接/,
            将$var數組順序倒置然後根據url分隔符拼接成字元串,拼接完畢後指派給$url*/
            $url    =   __APP__.'/'.($module?$module.MODULE_PATHINFO_DEPR:'').implode($depr,array_reverse($var));
        }

        /*檢測$urlCase的url大小寫是否敏感,true為不敏感*/
        if($urlCase){
            /*如果不敏感,則将$url轉化為小寫*/
            $url    =   strtolower($url);
        }

        /*檢測$vars是否為空*/
        if(!empty($vars)) { // 添加參數
            /*如果不為空,則進行循環,$var為循環的key,$val為循環的值*/
            foreach ($vars as $var => $val){
                /*檢查循環出來的$val值去掉兩端的空白字元後是否恒等于空,如果不為空,則
                $url後面拼接分隔符$depr和鍵$var分隔符$depr以及值$val*/
                if('' !== trim($val))   $url .= $depr . $var . $depr . urlencode($val);
            }                
        }

        /*檢測$suffix*/
        if($suffix) {
            /*檢測$suffix也就是字尾恒等于true,如果是則将URL_HTML_SUFFIX的配置指派給$suffix,
            如果不是true,則使用傳入的$suffix*/
            $suffix   =  $suffix===true?C('URL_HTML_SUFFIX'):$suffix;
            /*檢查$suffix裡|首次出現的位置并指派給$pos*/
            if($pos = strpos($suffix, '|')){
                /*$pos如果不為false,則将0到$pos之間的部分從$suffix裡裁剪出來指派給$suffix*/
                $suffix = substr($suffix, 0, $pos);
            }

            /*$url最後一位字元裁剪出來不為/,同時$suffix為true*/
            if($suffix && '/' != substr($url,-1)){
                /*将$suffix左側的.去除後前面拼接.後再在前面拼接$url*/
                $url  .=  '.'.ltrim($suffix,'.');
            }
        }
    }

    /*檢測是否存在錨點$anchor*/
    if(isset($anchor)){
        /*如果存在,則将其拼接在$url後,$anchor放在#後面*/
        $url  .= '#'.$anchor;
    }

    /*檢測是否存在域名$domain*/
    if($domain) {
        /*如果有則使用is_ssl()函數判定是http還是https協定,判定完拼接相應的協定加上域名以及url,最後指派給$url*/
        $url   =  (is_ssl()?'https://':'http://').$domain.$url;
    }

    /*傳回拼接好的url*/
    return $url;
}           

U函數運作流程圖,盡可能的省略但不影響表達,希望能有幫助,圖檔比較大,可以另外打開檢視:

詳解thinkphp的U函數及其源碼分析