ThinkPHP控制器学习(二)

作者: 阿V薄荷加可乐 | 来源:发表于2016-06-04 08:55 被阅读162次
    小样儿

    一、写在前面

    这几天准备写控制器中输出模板的方法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函数可以看出,虽然功能可以实现,但在细节上欠缺很多,如对请求类型的格式化,代码也有一定的冗余。看完了源码后,我也对其做了一定的修改,因为看着别人的源码写,总是会模仿它,所有就不再发出来了。但是,还是有很多收获的,下次再写的时候,自然会注意这些问题。

    好了,啰里啰唆一大堆,今天就写到这里了。因为我是类似于日记一样写的东西,可能,逻辑有点混乱。看本文的小伙伴请见谅,以后有时间,我再修改修改 :)

    相关文章

      网友评论

        本文标题:ThinkPHP控制器学习(二)

        本文链接:https://www.haomeiwen.com/subject/bvwhdttx.html