PHP闭包在类中的小笔记


在翻看 Laravel 源码时,发现其进行服务注册的时候,大量使用了诸如这样的格式:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php

//服务类父类
class Service{
public $app = '';
//所有服务类的构造方法
//$app 用于存放 容器类 的实例
public function __construct($app){
$this->app = $app;
}
}

//服务类A
class ServiceA extends Service{
//注册方法
public function register(){
$this->app->singleton(self::class, function($app){
echo 'Some action in this Closure ......';
});
}
}

//服务类B
class ServiceB extends Service{
//注册方法
public function register($args){
$this->app->singleton(self::class, function($app) use($args){
echo 'Some action in this Closure ......';
});
}
}

//容器类
//$bindings 存放各个服务类的实例
class Container{
public $bindings = [];

public function singleton($abstract, $concrete){
//注册各个服务类
$this->bindings[$abstract] = $concrete;
}
}

//入口
class Index{
public function __construct(){
//容器类
$Container = new Container();
$ServiceA = new ServiceA($Container);
$ServiceB = new ServiceB($Container);
//注册服务
$ServiceA->register();
$ServiceB->register([
'arg1' => 'value1'
]);
}
}

$Index = new Index();

以上代码中,Index类为入口,将服务类 ServiceAServiceB 的实例存放于 容器类 Container

这里值得注意的是,服务类在调用容器类 Container 的注册方法 singleton 时,第一个参数是注册的名字,第二个参数是一个匿名函数。

这样子操作,在容器类 Container 的 注册方法 singleton 中,$concrete 的值到底是什么呢?匿名函数的 Closure 实例吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
......
//服务类A
class ServiceA extends Service{
public function register(){
$this->app->singleton(self::class, function($app){
echo 'Some action in this Closure ......';
});
}
}

//容器类
//$bindings 存放各个服务类的实例
class Container{
public $bindings = [];

public function singleton($abstract, $concrete){
//注册各个服务类
$this->bindings[$abstract] = $concrete;
}
}
......

通过 Debuger 可以看到,$concrete 传入的确实是一个 Closure类,只不过这个类里头包含两个成员:$thisparameter。其中 $this 的值为 调用类$this。而 parameter 里头的值为 闭包 的 形参。如果使用了 use 语句,还会多一个 static 字段,用于存放 use 的变量

那传入闭包$bindings 有什么作用呢?可能这是一种设计模式吧。。还没怎么了解。。。Laravel 之后的代码中也确实通过类似下面的方式进行了调用。个人认为这样子相当于为服务类新增多一个单独的方法吧。可以这样子调用:

1
$Container->bindings[ServiceA::class]("xxxx");

更多高级点的知识参考下这篇文章

https://www.cnblogs.com/echojson/p/10957362.html


在 Laravel 中的相关操作

Laravel 中,有个叫 Container 的类,用于存放各个实例化的类,方便随时调用。

注册

为了将各个类注册进 Container 中,Container类有一个名为 bind 的方法专门用于将实例化的类存入 bindings 成员变量中。这些实例化的类有很多都是带着匿名函数的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 通过 Container 注册一个 binding
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
......
//如果传进来的实例不是一个匿名函数
//将会通过 $this->getClosure
//强行给实例添加一个匿名函数
//不过由于添加匿名函数时,$this 是 Container 类
//所以需要给 getClosure() 传入 $concrete。确保原来的类实例存在
if (! $concrete instanceof Closure) {
if (! is_string($concrete)) {
throw new \TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
}
//!!!!
$concrete = $this->getClosure($abstract, $concrete);
//!!!!
}
//注册实例
$this->bindings[$abstract] = compact('concrete', 'shared');
......
}

调用

调用已注册的 binding 使用的是 Container类 的 build()

1
2
3
4
5
6
7
8
9
public function build($concrete)
{
//如果传入的实例是匿名函数
if ($concrete instanceof Closure) {
//直接使用 变量() 的方式进行调用
return $concrete($this, $this->getLastParameterOverride());
}
......
}