ThinkPHP控制器学习(二)
一、写在前面
这几天准备写控制器中输出模板的方法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
函数可以看出,虽然功能可以实现,但在细节上欠缺很多,如对请求类型的格式化,代码也有一定的冗余。看完了源码后,我也对其做了一定的修改,因为看着别人的源码写,总是会模仿它,所有就不再发出来了。但是,还是有很多收获的,下次再写的时候,自然会注意这些问题。
好了,啰里啰唆一大堆,今天就写到这里了。因为我是类似于日记一样写的东西,可能,逻辑有点混乱。看本文的小伙伴请见谅,以后有时间,我再修改修改 :)