一、写在前面
这几天准备写控制器中输出模板的方法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_pArray( [user_name] => wangba [password] => ssfwjona )// $data_gArray ( [act] => publish ) 复制代码
5.2 参数过滤
传递系统过滤函数
$data_p = I('post.' , '' , 'htmlspecialchars');复制代码
结果:
Array( [user_name] =>wangba
[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]+$/')); 复制代码
结果:
结果1Array( [user_name] => [password] => )// 结果2wangba复制代码
filter_var的使用
// 传递参数 email=wangwuprint_r(I('post.email' , 'error' , 'FILTER_VALIDATE_EMAIL')); // 传递参数 email=wangba@qq.com print_r(I('post.email' , 'error' , 'FILTER_VALIDATE_EMAIL')); 复制代码
结果:
errorwangba@qq.com复制代码
5.3 修饰符的使用
// 转换为数组print_r(I('post.user_name/a' , 'error' )); 复制代码
结果
Array ( [0] => hello world ) 复制代码
5.4 额外数据请求类型
// 获取上传文件内容I('data.' , '' , '' , $_FILES);复制代码
六、总结
从自己写的I
函数可以看出,虽然功能可以实现,但在细节上欠缺很多,如对请求类型的格式化,代码也有一定的冗余。看完了源码后,我也对其做了一定的修改,因为看着别人的源码写,总是会模仿它,所有就不再发出来了。但是,还是有很多收获的,下次再写的时候,自然会注意这些问题。
好了,啰里啰唆一大堆,今天就写到这里了。因为我是类似于日记一样写的东西,可能,逻辑有点混乱。看本文的小伙伴请见谅,以后有时间,我再修改修改 :)