一、寫在前面
這幾天準備寫控制器中輸出模板的方法display
,但在閱讀源碼的時候遇到了一點問題。整個源碼我看了一遍,有幾部分理解有點問題,如系統鉤子類Hook
的原理,行為類ParseTemplateBehavior
中的checkCache
方法,Storage
的load
方法。
這些類與方法的源碼閱讀都遇到一些問題,并沒有能夠解決。因此現在就不說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']
同時支持僅傳遞元素參數,如id
,name
,act
,會自動判斷是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利用$_SERVER
的REQUEST_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
函數可以看出,雖然功能可以實現,但在細節上欠缺很多,如對請求類型的格式化,代碼也有一定的冗余。看完了源碼后,我也對其做了一定的修改,因為看著別人的源碼寫,總是會模仿它,所有就不再發出來了。但是,還是有很多收獲的,下次再寫的時候,自然會注意這些問題。
好了,啰里啰唆一大堆,今天就寫到這里了。因為我是類似于日記一樣寫的東西,可能,邏輯有點混亂。看本文的小伙伴請見諒,以后有時間,我再修改修改 :)