这篇文章是看到一个 问答贴 后的产物。
关于服务容器,我是看的 这篇文章 还有社区对该文章的一个 重新理解,文章从控制反转到工厂模式到依赖注入再到服务容器,讲的非常清楚。但是里面对于服务容器的 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 类。
