ThinkPHP控制器學習(二)

小樣兒

一、寫在前面

這幾天準備寫控制器中輸出模板的方法display,但在閱讀源碼的時候遇到了一點問題。整個源碼我看了一遍,有幾部分理解有點問題,如系統鉤子類Hook的原理,行為類ParseTemplateBehavior中的checkCache方法,Storageload方法。

這些類與方法的源碼閱讀都遇到一些問題,并沒有能夠解決。因此現在就不說display方法了,等以后看懂了再來說說display方法。

那么今天來說說TP的快捷函數I函數,它雖然不是Controller中的方法,但的確在控制器中需要接受數據的時候這個函數應用頗多。

二、I函數介紹

該函數在TP的自定義函數庫中算是比較長的,有134行代碼。先來簡單介紹這個方法。

I($name[,$default=''][,$filter=null][,$datas=null])

2.1 參數

變量名 變量類型 參數介紹
$name string 變量的名稱 支持指定類型
$default mixed 不存在的時候默認值
$filter mixed 參數過濾方法
$datas mixed 要獲取的額外數據源

name
變量的名稱,支持指定類型,格式為 變量類型.變量名/修飾符
post.表示取出POST數據,get.表示GET數據。支持通過點運算符來取出特定數據,如:

post.name   // 取出$_POST['name']
get.act     // 取出$_GET['act']  

同時支持僅傳遞元素參數,如idnameact,會自動判斷是POST,還是GET方式。

支持傳遞的類型有:

類型 含義
get 獲取GET參數
post 獲取POST參數
put 獲取PUT 參數
param 自動判斷請求類型獲取GET、 POST或者PUT參數
path 獲取 PATHINFO模式的URL參數
data 獲取 其他類型的參數, 需要配合額外數據源參數
request 獲取REQUEST 參數
session 獲取 $_SESSION 參數
cookie 獲取 $_COOKIE 參數
server 獲取 $_SERVER 參數
globals 獲取 $GLOBALS參數

支持以下修飾符的使用:

修飾符 作用
s 強制轉換為字符串類型
d 強制轉換為整型類型
b 強制轉換為布爾類型
a 強制轉換為數組類型
f 強制轉換為浮點類型

default
要取數據不存在的時候的默認值,可以傳遞一個報錯提示,使網站更具友好性,如

echo I('get.main' , 'hello');

如果沒取到main變量,就會返回hello

filter
可以傳遞一些參數過濾函數名作為參數,在I函數內就會回調用該函數來過濾取得的數據。這些過濾函數可以是PHP內置函數,也可以是自定義的函數。如:htmlspecialchars, addslashes

如果為空,自動調用系統默認的過濾函數,從DEFAULT_FILTER設置,如果傳遞了參數,則會忽略DEFAULT_FILTER

甚至支持使用正則表達式進行過濾,如:

print_r(I('post.user_name' , '' , '/^[A-Z][a-z]+$/')); 

如果匹配成功,則會將該變量返回,否則返回false

datas
要使用該參數,必須使$name的值為data,之后可以調用$datas來獲取其他方法的數據,如要獲取上傳文件的超全局變量$_FILES:

I('datas' , '' , '' , $_FILES);

三、自己寫一個I函數

可見I函數的功能很強大,但在看I函數源碼之前,不如先自己模仿著寫一個I函數,這樣看源碼就會有更深的理解:

function I($name , $default = '' , $filter = null , $datas = null) {

    if(!isset($name)) {
        return false;
    }

    // 判斷是否有修飾符存在
    if(strpos($name, '/')) {
        list($name , $type) = explode('/', $name);
    }

    // 如果有點號運算符 將取數據方式 與 要取得變量分開
    if(strpos($name, '.')) {
        list($method , $var) = explode('.', $name);
    } else {
        $method = $name;
    }

    // 判斷取參方式
    switch($method) {
        case 'post':
            $content = $_POST;
            break;

        case 'get':
            $content = $_GET;
            break;

        case 'session':
            $content = $_SESSION;
            break;

        case 'cookie' :
            $content = $_COOKIE;
            break;

        case 'request' :
            $content = $_REQUEST;
            break;

        case 'server' :
            $content = $_SERVER;
            break;

        case 'globals' :
            $content = $GLOBALS ;
            break;

        case 'data':
            $content = $datas;
            break;
        default:
            $content = false;
    }

    
    // 如果content不存在 且$default存在 直接返回default存在
    if(empty($content) && !empty($default)) {
        return $default;
    } 

    // 取出特定參數
    if(!empty($var)) {
        $content = $content[$var];
    }

    // 參數過濾
    if(!empty($filter)) {

        // 支持正則匹配 正則由'/'開頭
        if(strpos('/', $filter) == 0) {
            $flag = true;
            if(is_array($content)) {
                
                foreach ($content as $key => $value) {
                    // 匹配失敗 直接跳出
                    if(1 !== preg_match($filter,$value)) {
                        $flag = false;
                        break;
                    } 
                }

            } else {

                // 匹配成功 直接跳出
                if(1 !== preg_match($filter,$content)) {
                    $flag = false;
                } 
            }

            if(!$flag) {
                return empty($default)?'匹配失敗':$default;
            }
        } else {
            if(is_array($content)) {
            
                foreach ($content as $key => $value) {
                    $tmp[$key] = call_user_func($filter , $value);
                }

                $content = $tmp;
            } else {
                $content = call_user_func($filter , $content);
            }
        }
        

    }
    if(!empty($type)) {

        switch ($type) {
            case 's':
                $content = (String)$content;
                break;

            case 'd':
                $content = (Integer)$content;
                break;

            case 'b':               
                $content = (Boolean)$content;
                break;

            case 'a':
                $content = (Array)$content;
                break;

            case 'f':
                $content = (Float)$content;
                break;      
        }
    }
    
    return $content;
}

以上是自己模仿I函數寫的具有相似功能的函數:

支持 參數一 '類型.變量/修飾符'
不支持 path | param(自動判斷使用環境) | put 支持其他方式
不支持 使用filter_var 進行過濾
支持 $default 返回默認值
支持 傳遞過濾函數 正則匹配

這個函數還是有一些bug的,不能和TP里的函數比較,但作為學習其源碼的一個比較,還是足夠的。接下來,就來看看TP的I函數的源碼,觀察它的實現與自己寫的有什么不同之處。

四、兩個I函數的比較

寫完自己的I函數,現在就來看一看TP的I函數。這個函數有點長,為不浪費篇幅,就不放出來了。
觀其源碼,實現的步驟和自己寫的有點類似,但實現的細節有很多不同。

  • 修飾符 與 變量 與 類型分離
  • 判斷具體是那種傳遞數據的方式
  • 對數據進行處理:如過濾函數過濾變量 正則匹配變量 修飾符等

TP的I函數的變量類型比我自己寫的多了三個,path | param(自動判斷使用環境) | put,path是在TP的pathinfo模式下才起作用。我對與PUT方式不是很了解。所有這兩個都沒有加上。

4.1 自動判斷當前請求類型的實現

自動判斷使用環境,TP利用$_SERVERREQUEST_METHOD元素來完成的,我并沒有想到這個屬性,因此在自己的I函數中并沒有該功能。這個元素記錄的是訪問頁面使用的方法,有PUT , GET , POST , HEAD。源代碼如下:

 switch($_SERVER['REQUEST_METHOD']) {
    case 'POST':
        $input  =  $_POST;
        break;
    case 'PUT':
        if(is_null($_PUT)){
            parse_str(file_get_contents('php://input'), $_PUT);
        }
        $input  =   $_PUT;
        break;
    default:
        $input  =  $_GET;
}

引用傳遞的使用
TP在獲取變量請求類型時并不是直接賦值,而是使用了引用傳遞,而我個人對引用傳遞理解不深,就不在這里穿鑿附會了。

4.2 正則過濾的實現

TP的I函數的正則過濾僅在傳遞了具體變量時起作用,如下:

 if(0 === strpos($filters,'/')){
    if(1 !== preg_match($filters,(string)$data)){
        // 支持正則驗證
        return   isset($default) ? $default : null;
    }
}else{
    $filters    =   explode(',',$filters);                    
}

可以看出TP在判斷是不是正則的方法與我一致,但它的正則過濾僅僅在傳遞了具體的變量名才能使用,如果是一個數組,就會返回空,如下:

print_r(I('post.' , '' , '/^[A-Za-z]+$/'));
// 打印
Array
(
    [user_name] => 
    [password] => 
)

4.3 filter_var 的調用

Filter函數是PHP內置的函數庫,用于特定的過濾,如email,callback等,要使用filter_var,傳遞的變量,就必須是filter_list中的內容。

 $data   =   filter_var($data,is_int($filter) ? $filter : filter_id($filter));

還有很多細節的實現有不同之處,可以看出我自己寫的函數在細節上還有不少問題,如對變量的處理并沒有將其轉同一的大小寫等等。

五、應用

看完了源碼,甚至模擬了一個I函數,現在就來說說I的具體應用。

5.1 普通應用

// 獲取post 數據
$data_p = I('post.');
// 獲取get數據
$data_g = I('get.');

結果

// $data_p
Array
(
    [user_name] => wangba
    [password] => ssfwjona       
)
// $data_g
Array ( [act] => publish ) 

5.2 參數過濾

傳遞系統過濾函數

$data_p = I('post.' , '' , 'htmlspecialchars');

結果:

Array
(
    [user_name] => <p>wangba</p>
    [password] => sfkhanasf
)

傳遞自定義過濾參數

print_r(I('post.' , '' , '_addslashes'));

結果:

Array
(
    [user_name] => wang\"haf
    [password] => safegawh
)

正則表達式的使用

// 如果傳遞數組
print_r(I('post.' , '' , '/^[A-Za-z]+$/'));            
// 如果傳遞特定值
print_r(I('post.user_name' , '' , '/^[A-Z][a-z]+$/'));                

結果:

結果1
Array
(
    [user_name] => 
    [password] => 
)
// 結果2
wangba

filter_var的使用

// 傳遞參數 email=wangwu
print_r(I('post.email' , 'error' , 'FILTER_VALIDATE_EMAIL')); 
// 傳遞參數 email=wangba@qq.com 
print_r(I('post.email' , 'error' , 'FILTER_VALIDATE_EMAIL'));        

結果:

error
wangba@qq.com

5.3 修飾符的使用

// 轉換為數組
print_r(I('post.user_name/a' , 'error' ));   

結果

Array ( [0] => hello world ) 

5.4 額外數據請求類型

// 獲取上傳文件內容
I('data.' , '' , '' , $_FILES);

六、總結

從自己寫的I函數可以看出,雖然功能可以實現,但在細節上欠缺很多,如對請求類型的格式化,代碼也有一定的冗余。看完了源碼后,我也對其做了一定的修改,因為看著別人的源碼寫,總是會模仿它,所有就不再發出來了。但是,還是有很多收獲的,下次再寫的時候,自然會注意這些問題。

好了,啰里啰唆一大堆,今天就寫到這里了。因為我是類似于日記一樣寫的東西,可能,邏輯有點混亂。看本文的小伙伴請見諒,以后有時間,我再修改修改 :)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,775評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,738評論 18 399
  • 秋聲遠了 孤琴彈不出迢遙的夢 一曲憂歌也已啞音 荒蕪的原野 寂寞的守夢人 認得這心中的懷念 在舊時的琴調 也許是繽...
    木子愛若閱讀 279評論 13 12
  • 1. 從一棵被肆意收割的韭菜,要想成為一起去收割韭菜的農夫,除了經驗的積累,更多的是心態的成熟。漸漸的會發現,操盤...
    大陽向錢沖閱讀 332評論 0 0