一、什么是闭包?
- 首先闭包是一个函数,它能够读取其他函数内部的变量。
- 一般的高级编程语言中,(不通过特殊方法)只有在函数内部才能够读取到其中的局部变量
- 我们可以将闭包理解成一个定义在函数内部的子函数。
- 本质上闭包是将函数内部与函数外部连接起来的桥梁
二、PHP中的闭包
1. 思考为什么要使用闭包?
面向对象的语言对代码的复用功能上:是用类来实现的,类中的代码复用使用继承来实现。那面向函数的代码复用是如何实现的?这就是为什么我们要使用闭包的原因,解决函数的复用问题。
PHP中的闭包和javascript很类似,通过使用 use 关键字来实现。
2. Closure类
查看手册,Closure类用于代表匿名函数。匿名函数在PHP5.3被引入,以前有一个create_function的函数. 它的结构是这样的 string create_function ( string
$args
, string$code
) ,第一个参数是我们创建的这个函数的参数,第二个参数是,我们将使用的代码结构。可是两个参数的类型都是String,你让一个稍微复杂的函数的函数体用引号来包裹一定1看起来就不优雅。所以就有了匿名函数 PHP 会自动将闭包函数转换成内置类Closure类的对象
- Closure::bind — 复制一个闭包,绑定指定的$this对象和类作用域。
- Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。
3. 测试 bind()
- 先创建一下基本的测试环境代码
<?php
// 创建一个用于测试的类
class Product
{
public $name;
protected $no;
private $price;
function __construct($name, $no, $price)
{
$this->name = $name;
$this->no = $no;
$this->price = $price;
}
public function getSummaryLine()
{
$base = '商品名称:' . $this->name;
$base .= '<br>商品编号:' . $this->name;
$base .= '<br>商品价格:' . $this->name;
return $base;
}
}
function show_format($variable)
{
echo "<pre>";
print_r($variable);
echo "<hr/></pre>";
}
- 准备三个不同的匿名函数,来访问商品类的三个属性
- 商品名称-public
- 商品编号-no
- 商品价格-price
<?php
// 匿名函数
$funcName = function () {
return $this->name;
};
$funcNo = function () {
return $this->no;
};
$funcPrice = function () {
return $this->price;
};
- 先使用 Closure::bind() ,来绑定第一个匿名函数变量$funcName,看看返回的结果.
- 从返回结构上看,它是一个对象,确实绑定了Product类的$this的作用域
<?php
$res1 = Closure::bind($funcName, new Product('烤串', 1001, '10 RMB'), 'Product');
show_format($res1);
- 输出其中的属性
- 我们发现都能够输出,无论是什么 public、protected、private,仔细一想,这不废话吗?它绑定后相遇与将Product类的伪变量$this的作用域是一样的
<?php
$res1 = Closure::bind($funcName, new Product('烤串', 1001, '10 RMB'), 'Product');
show_format($res1());
$res2 = Closure::bind($funcNo, new Product('烤串', 1001, '10 RMB'), 'Product');
show_format($res2());
$res3 = Closure::bind($funcPrice, new Product('烤串', 1001, '10 RMB'), 'Product');
show_format($res3());
- 我们稍稍改造一下Product类,看看它访问不同权限的修饰的方法的结果如何,不出意外的话应该和上面的结果如出一辙。
<?php
// 创建一个用于测试的类
class Product
{
public function A()
{
return 'A()';
}
protected function B()
{
return 'B()';
}
private function C()
{
return 'C()';
}
}
function show_format($variable)
{
echo "<pre>";
print_r($variable);
echo "<hr/></pre>";
}
// 匿名函数
$funcA = function () {
return $this->A();
};
$funcB = function () {
return $this->B();
};
$funcC = function () {
return $this->C();
};
$res1 = Closure::bind($funcA, new Product(), 'Product');
show_format($res1());
$res2 = Closure::bind($funcB, new Product(), 'Product');
show_format($res2());
$res3 = Closure::bind($funcC, new Product(), 'Product');
show_format($res3());
- 实际上如果我不加上第三个参数会报错
- 所以可选的第三个参数如手册上面所说:绑定给闭包的类作用域, ‘static’ 表示不改变(static 也是默认值)。如果传入一个对象,则使用这个对象的类型名。 类作用域用来决定在闭包中 $this 对象的 私有、保护方法 的可见性。
<?php
$res3 = Closure::bind($funcB, new Product());
show_format($res3());
// Fatal error: Uncaught Error:
// Call to protected method Product::B() from context 'Closure'
- 上面说,如果传入一个对象,则使用这个对象的类型名称,我们来试一下.
- 就结果来说,手册果然诚不欺我,就是PHP手册的解释和例子过于垃圾,不让人容易理解。
<?php
$res3 = Closure::bind($funcB, new Product(), new Product());
show_format($res3());
$res4 = Closure::bind($funcB, new Product(), 'Product');
show_format($res4());
- 闲着没事,我们继续改造一下Product类,来测试一下静态的方法。
- 果然不出所料,是一样的效果。
<?php
<?php
// 创建一个用于测试的类
class Product
{
public static function A()
{
return 'A()';
}
protected static function B()
{
return 'B()';
}
private static function C()
{
return 'C()';
}
}
function show_format($variable)
{
echo "<pre>";
print_r($variable);
echo "<hr/></pre>";
}
// 匿名函数
$funcA = function () {
return $this->A();
};
$funcB = function () {
return $this->B();
};
$funcC = function () {
return $this->C();
};
$res1 = Closure::bind($funcA, new Product(), 'Product');
show_format($res1());
$res2 = Closure::bind($funcB, new Product(), 'Product');
show_format($res2());
$res3 = Closure::bind($funcC, new Product(), new Product());
show_format($res3());
4. 测试bindTo()
- 熟悉bind()后我们再去看看bindTo(),感觉这两个方法应该差不多才对
- public Closure Closure::bindTo ( object
$newthis
[, mixed$newscope
= ‘static’ ] )- — 复制当前闭包对象,绑定指定的$this对象和类作用域。
- 我们先来正常的使用,即使是这一个简单的例子我们也可也回顾到以前的好多知识点
- PHP中对于Public修饰的方法还是很宽松的,就算它是静态的我们也可以使用对象来调用。反过来这个方法时public修饰的它不是静态的,我们其实也可以静态去调用。看起来这并不是我们所期望的
- 一般调用静态方法
- 在类的外部:我们使用类名::类方法来调用
- 在类的内部:
- 使用self::类方法来调用
- 使用类名::类方法来调用
- 使用static::类方法来调用也是可行,下一节需要仔细理解一下PHP中的静态延迟绑定.这里不说
- 我们也是可以在类的内部这么创建对象
- self className()
- new className()
- static className()
<?php
class Product
{
public static function A()
{
echo 'A() <hr/>';
}
protected static function B()
{
echo 'B() <hr/>';
}
private static function C()
{
echo 'C() <hr/>';
}
public static function B_B()
{
self::B();
}
public static function C_C()
{
static::C();
}
}
function show_format($variable)
{
echo "<pre>";
print_r($variable);
echo "<hr/></pre>";
}
(new Product())->A();
(new Product())->B_B();
(new Product())->C_C();
- 继续,我们使用 bindTo()来试一下
- 使用手册上面的代码,没啥头绪,先暂停
<?php
<?php
class A
{
function __construct($val)
{
$this->val = $val;
}
function getClosure()
{
//returns closure bound to this object and scope
return function () {
return $this->val;
};
}
}
$ob1 = new A(1);
$res1 = $ob1->getClosure();
echo $res1(), "<br>"; // 1
$res2 = $res1->bindTo($ob1);
echo $res2(), "<br>"; // 1
5. PHP中闭包和匿名函数的关系
- 其实我们早就应该看出来,在PHP中匿名函数就是闭包,虽然在概念上这二者是根本不一样的
- 返回值中 Closure Object,早就告诉了我们
<?php
<?php
error_reporting(E_ALL);
function print_format($vaeiable)
{
print_r('<pre>') . print_r($vaeiable) . print_r('</pre>');
}
$func1 = function ($a, $b) {
return $a + $b;
};
print_format($func1);
/*
Closure Object
(
[parameter] => Array
(
[$a] =>
[$b] =>
)
)
*/
- 当然我们亦可以传递参数 print_format($func1(1,2)); // 3 就像调用普通函数一样
6. use一桥梁
- 当匿名函数需要使用外部的变量时,如下代码会报错误,一个Notice的提醒,我们找不到这个变量。
- 当然这都在我们的意料之中,因为普通函数也不行啊,这是函数的作用域的问题啊
<?php
$outVariable = 7;
$func1 = function ($a, $b) {
return $a + $b + $outVariable;
};
print_format($func1(1,2)); // 3
- 我现在头铁,我偏不用 use 关键字,使用global关键字,试试匿名函数中能否行的通.
- 结果当然是可行的了,在我们的意料之中啊
<?php
$outVariable = 7;
function func1 ($a, $b) {
global $outVariable;
return $a + $b + $outVariable;
};
print_format(func1(1,2)); // 10
- 然后我继续头铁,我们将变量定义为静态的全局变量
- 在向一个奇怪的地方发展 。。。。。我的用法违背了初衷
<?php
$outVariable = 7;
function func1 ($a, $b) {
static $outVariable;
return $a + $b + $outVariable++;
};
print_format(func1(1,2)); // 3
print_format(func1(1,2)); // 4
print_format(func1(1,2)); // 5
print_format(func1(1,2)); // 6
- 我们使用use来写
- 代码如下,所以对于PHP中的匿名函数(闭包),use就像是一个桥梁一样
<?php
$outVariable = 7;
$func1 = function ($a, $b) use ($outVariable) {
return $a + $b + $outVariable;
};
print_format($func1(1, 2)); // 10
- 使用use ,后匿名函数能否改变外部变量,代码如下
- 果然这么使用是不行的,意料之中
<?php
$outVariable = 0;
$func1 = function () use ($outVariable) {
return ++$outVariable;
};
print_format($func1()); // 1
print_format($func1()); // 1
print_format($func1()); // 1
- 如果加上取地址符会怎么样?
- 果然可行,这就说明了一个问题 use 关键字其实就相当于拷贝了外部变量的副本,就像是 clone() 方法一样,并不是变量的引用地址
<?php
$outVariable = 0;
$func1 = function () use (&$outVariable) {
return ++$outVariable;
};
print_format($func1()); // 1
print_format($func1()); // 2
print_format($func1()); // 3
- 对于多个变量也是可以
<?php
$outVariable1 = 1;
$outVariable2 = 2;
$func1 = function () use ($outVariable1,$outVariable2) {
return $outVariable1 + $outVariable2;
};
print_format($func1()); // 3
7.小结一PHP中的闭包
- PHP中将匿名函数和闭包视作同一概念,虽然这并不是的
- PHP中的闭包、匿名函数、不同函数都是看起来函数,句法相同。闭包和匿名函数其实是Closure类对象的实例,只不过PHP表面上伪装了一下
- 闭包函数是为了函数的复用,闭包是函数外部于内部连接的桥梁。PHP中匿名函数+use实现了闭包,其实将匿名函数当参数传入也相当于实现了闭包。
- PHP中的闭包是对象,通过伪变量$this,可以获取闭包内部的状态。它的内部没有属性,只有三个我们常用的方法
- Closure::__construct — 用于禁止实例化的构造函数
- Closure::bind — 复制一个闭包,绑定指定的$this对象和类作用域。
- Closure::bindTo — 复制当前闭包对象,绑定指定的$this对象和类作用域。
- 后两个方法大致类似,注意最后一个参数是用来设定作用域的
- 闭包对象实现了 __invloek()这个魔术方法
- use 是匿名函数连接外部的桥梁,它仅仅拷贝的事变量的值,而非引用,如果实头铁,加上取地址符就好了。
三、PHP中哪些地方会用到闭包函数
1.返回一个函数结构
- 使用array_reduce()函数求和,一般的函数使用
<?php
$arr = [1, 2, 3, 4, 5];
function sum($arr)
{
return array_reduce($arr, function ($x, $y) {
return $x + $y;
});
}
print_r(sum($arr));
- 使用闭包
<?php
$arr = [1, 2, 3, 4, 5];
function lazySum($arr)
{
return function () use ($arr) {
return array_reduce($arr, function ($x, $y){
return $x + $y;
});
};
}
print_r(lazySum($arr)());
- PHP中的一道闭包面试题
<?php
function plus()
{
$funcArr = [];
for ($i = 1; $i <= 3; $i++) {
$funcArr[] = function () use (&$i) {
return $i * $i;
};
}
return $funcArr;
}
$res = plus();
var_dump($res[0]()); // 16
var_dump($res[1]()); // 16
var_dump($res[2]()); // 16