PHP中闭包Closure::bind详解

556 阅读6分钟
tp6、laravel底层代码时  ,发现代码中很多Closure::bind用法,查询各种资料,一直对它的使用一直半解(网上大多都是抄袭教材,解释的不清楚),还好经过我不懈努力,终于弄懂了其中道理。

1、在正式解释前我们先了解一些基础的东西(稍安勿躁,磨刀不误砍柴工。大多说看不懂其用法的人,主要还是基础不牢)。php中 闭包我们也可以叫做匿名函数,匿名函数不了解的可以自行补一下,我这边简单举例:

$say = function(){
    return '我是匿名函数';
};

//echo  $say(); //这是最直接调用匿名函数方式  输出 我是匿名函数

function  test(Closure $callback){
    return $callback();
}
echo test($say); //这是间接调用匿名函数方式  同样输出 我是匿名函数

当然也可以这样写 :

echo test( function(){
   return '我是匿名函数';
 });

2、接着我们来说一下php的public、protected、private三种访问控制模式的区别

public: 公有类型 在子类中可以通过self::var调用public方法或属性,parent::method调用父类方法

    在实例中可以能过$obj->var 来调用 public类型的方法或属性

protected: 受保护类型在子类中可以通过self::var调用protected方法或属性,parent::method调用父类方法

    在实例中不能通过$obj->var 来调用  protected类型的方法或属性

private: 私有类型该类型的属性或方法只能在该类中使用,在该类的实例、子类中、子类的实例中都不能调用私有类型的属性和方法

    <?php
    
    class A{

        private $name = '王力宏';

        protected $age = '30';

        private static $weight = '70kg';

        public $address = '中国';

        public static $height = '180cm';

    }


    $obj = new A();

    //echo $obj->name;//报错 Cannot access private property A::$name

    //echo $obj->age;//报错 Cannot access protected property A::$age

    //echo A::$weight; //报错 Cannot access private property A::$weight

    echo $obj->address;//正常 输出 中国

    echo A::$height;//正常 输出 180cm

    $fun = function(){

    $obj = new A();

    return $obj->address;//实例对象可以获得公有属性,  $obj->name等私有属性肯定不行 上面例子已列出报错

    }

    echo $fun();//正常 输出 中国

    $fun2 = function(){

            return A::$height;//  类可以直接访问公有静态属性,但A::$weight肯定不行,因为weight为私有属性

    }

    echo $fun2();//正常 输出 180cm

    

3、在以上都理解的情况下 我们来看看这样的情况,有如下匿名函数:

$fun = function(){

    return $this->name;

}

或者

$fun = function(){

        return A::$height;

}

echo $fun();//会报错的

其实单独这段代码是肯定不能运行 调用的,因为里面有个$this,程序压根不知道你这个$this是代表那个对象 或 那个类(并且就算知道那个对象或类,该对象是否拥有name属性,如果没有照样会有问题)

因此想让其正常运行肯定有前提条件啊(就好比你想遍历某个数组一样,如果这个数组压根你就没提前定义 声明 肯定会报错的)

如果这样:


<?php

class A{

private $name = '王力宏';

    protected $age = '30';

    private static $weight = '70kg';

    public $address = '中国';

    public static $height = '180cm';

}


$fun = function(){

    return $this->name;

} ;

$cf = Closure::bind($fun,new A() ,'A');   //该函数返回一个新的 Closure 对象 或者在失败时返回 FALSE                                                                                                                                                            

echo $cf();//输出 王力宏  为什么呢:bind第一个参数是匿名函数,第二个A实例对象,第三个代表作用域

//网上很多讲解是说把匿名函数绑定到了实例对象上了  然后就怎么着......很多新手看了依然一脸懵逼。其实是匿名函数里的$this被指定到了或绑定到了A实例对象上了

Closure::bind($fun,new A() );这个使用 你可以理解成对匿名函数做了如下过程:



$fun = function(){

     $this = new A();//你可以想象认为,$this就成了A类的实例对象了,然后在去访问name属性,就和我们正常
     //实例化类访问成员属性一样,上面2中的例子$obj = new A()就是这样,(因为$this是关键字,在这里我们其
     //实不能直接$this = new A();这么写,为了好理解我写成$this,但是原理还是这个意思),但是我们都知道
     //因为name属性是私有的,上面2中我已说过,实例对象不能访问私有属性,那该怎么办呢,于是添加第三个参
     //数就很重要了,一般传入传入一个对应对象,或对应类名(对应的意思是:匿名函数中$this-name想获取name
     //属性值,你这个$this想和那个类和对象绑定在一起呢,就是第二个参数,这时你第三个参数写和第二个参数
     //写一样的对象或类就行了,就是作用域为这个对象或类,这就会让原理的name私有属性变为公有属性)

    return $this->name;

} ;

为了加深理解我在举两个例子:

<?php


class A{

    private $name = '王力宏';

    protected $age = '30';

    private static $weight = '70kg';

    public $address = '中国';

    public static $height = '180cm';



}



$fun = function(){

    $obj = new A();

    return $obj->address;

};



echo $fun();//正常 输出 中国 ,该例子其实在上面2中已经讲过了 如果跟这个还不能理解 就不要往下看了 好好补一下基础



$fun2 = function(){

    return $this->address;

};

//echo $fun2();这样运行会报错  所以我们要进行匿名函数的绑定  是匿名函数里的$this有所指,**必要时还要改变要访问属性的作用域**

$newfun2 = Closure::bind($fun2, new A() );//使用了该函数后 ,该函数返回一个全新的匿名的函数,和原来匿名函数$fun2一模一样,只是其中 $this被指向了A实例对象,这样就能访问address属性了



echo $newfun2();//正常 输出 中国,在这里数一下bind这次为什么没添加第三个参数,因为我们要访问的address属性是公有的,一个对象实例是可以直接访问公有属性的,这个例子中只要匿名函数中$this被指向了A对象实例(或者叫绑定也可以),就能访问到公有属性,所以可以不用添加第三个参数,当然你加上了第三个参数 如这样Closure::bind($fun2, new A(), ‘A’ );或Closure::bind($fun2, new A(), nwe A() );不影响 照样运行,就好比把原来公有属性 变为公有属性 不影响的(一般当我们访问的属性为私有属性时,才使用第三个参数改变作用域 ,使其变为公有属性)


下面来看最后一例子,再次加深一下理解:

<?php

class A{

    private $name = '王力宏';

    protected $age = '30';

    private static $weight = '70kg';

    public $address = '中国';

    public static $height = '180cm';


}

$fun = function(){

    return A::$weight;

};

//echo $fun();   运行会报错 因为weight为私有属性  上面2中例子访问height属性是可以的,height为公有属性,所以把weight改成height是可以正常运行的,但是我们现在就想访问这个私有静态属性,我们该怎么办,于是Closure::bind出场了

$newfun = Closure::bind($fun,null,'A');//通过bind函数作用,返回一个和$fun匿名函数一模一样的匿名函数,只是该匿名函数中A::$weight,       weight属性由私有变成公有属性了。

echo $newfun();// 正常 输出 70kg  ,为什么第二个参数又成null了呢,因为在该匿名函数中A::$weight 这属于
//正常类使用啊(php中  类名::公有静态属性,这是正常访问方法,上面2中例子已经说的很清楚了),所以不用绑定
//到某个对象上去了,于是第二个参数可以省略,唯一遗憾的是weight属性虽是静态属性,但是其权限是private私有
//属性,于是我们要把私有属性变公有属性就可以了,这时把第三个参数加上去就可以了,第三个参数可以是A类
//(Closure::bind($fun,null,'A')),也可以是A类的对象实例(Closure::bind($fun,null, new A() )),两种
//写法都可以,最终第三个参数的添加使私有属性变成了公有属性,(这个例子中当然你非得添加第二个参数肯定也没
//问题,只要第二个参数是A的实例对象就行Closure::bind($fun,new A(),'A'),不影响,只是说  A::$weight 
//这种使用方法本身就是正常使用,程序本身就知道你用的是A类,你在去把它指向到A类自己的对象实例上,属于多此
//一举,因此第二个参数加不加都行,不加写null就行)

综上大家应该理解其用法了吧,有时第二个参数为null,有时第三个参数可以不要,这些都跟你匿名函数里 代码中访问的方式紧密相关

总结:

1、一般匿名函数中有$this->name类似这样用 $this访问属性方式时,你在使用bind绑定时 ,第二个参数肯定要写,写出你绑定那个对象实例,第三个参数要不要呢,要看你访问的这个属性,在绑定对象中的权限属性,如果是private,protected 你要使用第三个参数 使其变为公有属性, 如果本来就是公有,你可以省略,也可以不省略

2、一般匿名函数中是  类名::静态属性  类似这样的访问方式(比如例子中A::$weight),你在使用bind绑定时,第二个参数可以写null,也可以写出具体的对象实例,一般写null就行(写了具体对象实例多此一举),第三个参数写不写还是得看你访问的这个静态属性的权限是 private 还是 public,如果是私有private或受保护protected的,你就得第三个参数必须写,才能使其权限变为公有属性 正常访问,如果本来就是公有public可以不用写,可以省略