最近在看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可以不用写,可以省略
**注:上面如有错别字或运行不正常的情况,请自己在使用时稍作修改即可,文章我已审核过,应该不会有问题,毕竟写这么多文字难免会有手误,请谅解!**