这篇文章是看到一个 问答贴 后的产物。
关于服务容器,我是看的 这篇文章 还有社区对该文章的一个 重新理解,文章从控制反转到工厂模式到依赖注入再到服务容器,讲的非常清楚。但是里面对于服务容器的 demo 实例,却让初学者很难看懂(比如我)。
本片文章就是我对该 demo 的理解。
服务容器 demo 代码:
class Container
{
// 绑定闭包函数
protected $binds;
// 绑定实例
protected $instances;
public function bind($abstract, $concrete)
{
// 如果 $concreate 是一个闭包函数
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
public function make($abstract, $parameters = [])
{
// 如果 $abstract 实例存在,则返回该实例
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
// 将 $this 插入到 $parameters 数组中
array_unshift($parameters, $this);
// 返回绑定的闭包函数,并将 $parameters 传递为该闭包函数的参数。
return call_user_func_array($this->binds[$abstract], $parameters);
}
}
这里简单的说一下上述代码中的 PHP 库函数代码:
- $concrete instancedof Closure
判断 $concrete 是否为 Closure 的实例。Closure 为 PHP 预定义 final 类,用于实现匿名函数,也叫闭包函数。也就是判断 A 是否为闭包函数.
- isset(abstract])
检查 abstract] 是否已设置并且非NULL
- array_unshift($parameters, $this)
在 $paramenters 数组开头插入 $this 。
- call_user_func_array(abstract], $parameters)
调用闭包函数 abstract] ,并将 $parameters 数组作为函数的参数。该函数需要注意传入的数组必须为索引数组,因为传参是一个一个索引值,而不是整个数组。具体情况可以看文档。
实际业务代码:
interface Boards {
public function type();
}
class CommonBoard implements Boards {
public function type(){
echo '普通键盘';
}
}
class MechanicalKeyboard implements Boards {
public function type(){
echo '机械键盘';
}
}
class Computer {
protected $keyboard;
public function __construct (Boards $keyboard) {
$this->keyboard = $keyboard;
}
}
首先我们实例化一个 Container
$container = new Container();
我们先来看看 bind 函数
public function bind($abstract, $concrete)
{
// 如果 $concreate 是一个闭包函数
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
首先判断 $concrete 是否为闭包函数,如果为闭包函数则会将 闭包函数 $concrete 绑定到 binds 数组上,并且索引为 $abstract。例如:
$container->bind('Board', function() {
return new CommonBoard();
});
// 上述代码将 Board 和该闭包函数绑定
如果不为闭包函数,则会绑定到 instances 数组。通常为一个实例,例如:
$container->bind('BoardInstance', new CommonBoard());
// 上述代码将 'BoardInstance' 和 该实例绑定
再来解释一下 make 函数,make 顾名思义为产生,制造。
public function make($abstract, $parameters = [])
{
// 如果 $abstract 实例存在,则返回该实例
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
// 将 $this 插入到 $parameters 数组中
array_unshift($parameters, $this);
// 返回绑定的闭包函数,并将 $parameters 传递为该闭包函数的参数。
return call_user_func_array($this->binds[$abstract], $parameters);
}
进入函数,先判断 $instances 数组中是否有 $abstract 这个元素,如果存在就直接返回该元素。例如:
$computer1 = $container->make('BoardInstance'); // return new CommonBoard();
因为上面我们已经绑定了 BoardInstance ,所以很显然 $this->instances['BoardInstance ']
是存在的,所以这里返回绑定的实例。
如果是这样
$computer2 = $container->make('Board'); // return function() { return new CommonBoard();};
Board 没有绑定 instances 数组,直接看 array_unshift()
,这里 $parameters
未传值为空,array_unshift
就相当与将 $this
赋值给 $parameters
。
注:此处并没有凸显 array_unshift 的实际用途。
紧接着,返回该绑定的回调(闭包)函数,并将 $parameters 数组作为参数传递给该回调函数。
虽然 $computer1
和 $computer2
返回的实例相同,但是确实通过不同的过程产生的。
在上面的讲解中,你可能觉得 $parameters
数组和 array_unshift
函数用处不大,这是因为没有用在刀刃上,我们看下面的例子:
$container->bind('Computer', function($container, $modleName) {
return new Computer($conrainer->make($modleName));
});
可以看到相对于上述例子,这里最大的变化是在匿名函数里进行了一次 make (创建) 。可以看成 Computer 类是被服务容器来注入的。
调用也非常简单:
$computer3 = $container->make('Computer', ['Board']);
一层一层的来分析:
首先 instances 数组中没有该字符串,直接看下一步。
由于这里传入了 parameters 数组可以看成这样:[Conrainer 实例,’Board’]。
故,我们通过 call_user_func_array()
来调用闭包函数时,可以传入两个参数。就和 bind
函数里绑定的参数一样,$container 对应 Container 实例, $modleName 对应 ‘Board’ 字符串.
所以对于匿名函数中返回的 Computer
new Computer($container->make($modleName));
可以看成
new Computer($container->make('Board');
又是一层 make 函数,和上面一样,这里不再深究。
所以我们的 $computer3 返回的类型为自动注入了 CommonBoard 的 Computer 类。