这篇文章是看到一个 问答贴 后的产物。
    关于服务容器,我是看的 这篇文章 还有社区对该文章的一个 重新理解,文章从控制反转到工厂模式到依赖注入再到服务容器,讲的非常清楚。但是里面对于服务容器的 demo 实例,却让初学者很难看懂(比如我)。
    本片文章就是我对该 demo 的理解。
    服务容器 demo 代码:

    1. class Container
    2. {
    3. // 绑定闭包函数
    4. protected $binds;
    5. // 绑定实例
    6. protected $instances;
    7. public function bind($abstract, $concrete)
    8. {
    9. // 如果 $concreate 是一个闭包函数
    10. if ($concrete instanceof Closure) {
    11. $this->binds[$abstract] = $concrete;
    12. } else {
    13. $this->instances[$abstract] = $concrete;
    14. }
    15. }
    16. public function make($abstract, $parameters = [])
    17. {
    18. // 如果 $abstract 实例存在,则返回该实例
    19. if (isset($this->instances[$abstract])) {
    20. return $this->instances[$abstract];
    21. }
    22. // 将 $this 插入到 $parameters 数组中
    23. array_unshift($parameters, $this);
    24. // 返回绑定的闭包函数,并将 $parameters 传递为该闭包函数的参数。
    25. return call_user_func_array($this->binds[$abstract], $parameters);
    26. }
    27. }

    这里简单的说一下上述代码中的 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 数组作为函数的参数。该函数需要注意传入的数组必须为索引数组,因为传参是一个一个索引值,而不是整个数组。具体情况可以看文档。

    实际业务代码:

    1. interface Boards {
    2. public function type();
    3. }
    4. class CommonBoard implements Boards {
    5. public function type(){
    6. echo '普通键盘';
    7. }
    8. }
    9. class MechanicalKeyboard implements Boards {
    10. public function type(){
    11. echo '机械键盘';
    12. }
    13. }
    14. class Computer {
    15. protected $keyboard;
    16. public function __construct (Boards $keyboard) {
    17. $this->keyboard = $keyboard;
    18. }
    19. }

    首先我们实例化一个 Container

    1. $container = new Container();

    我们先来看看 bind 函数

    1. public function bind($abstract, $concrete)
    2. {
    3. // 如果 $concreate 是一个闭包函数
    4. if ($concrete instanceof Closure) {
    5. $this->binds[$abstract] = $concrete;
    6. } else {
    7. $this->instances[$abstract] = $concrete;
    8. }
    9. }

    首先判断 $concrete 是否为闭包函数,如果为闭包函数则会将 闭包函数 $concrete 绑定到 binds 数组上,并且索引为 $abstract。例如:

    1. $container->bind('Board', function() {
    2. return new CommonBoard();
    3. });
    4. // 上述代码将 Board 和该闭包函数绑定

    如果不为闭包函数,则会绑定到 instances 数组。通常为一个实例,例如:

    1. $container->bind('BoardInstance', new CommonBoard());
    2. // 上述代码将 'BoardInstance' 和 该实例绑定

    再来解释一下 make 函数,make 顾名思义为产生,制造。

    1. public function make($abstract, $parameters = [])
    2. {
    3. // 如果 $abstract 实例存在,则返回该实例
    4. if (isset($this->instances[$abstract])) {
    5. return $this->instances[$abstract];
    6. }
    7. // 将 $this 插入到 $parameters 数组中
    8. array_unshift($parameters, $this);
    9. // 返回绑定的闭包函数,并将 $parameters 传递为该闭包函数的参数。
    10. return call_user_func_array($this->binds[$abstract], $parameters);
    11. }

    进入函数,先判断 $instances 数组中是否有 $abstract 这个元素,如果存在就直接返回该元素。例如:

    1. $computer1 = $container->make('BoardInstance'); // return new CommonBoard();

    因为上面我们已经绑定了 BoardInstance ,所以很显然 $this->instances['BoardInstance '] 是存在的,所以这里返回绑定的实例。
    如果是这样

    1. $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 函数用处不大,这是因为没有用在刀刃上,我们看下面的例子:

    1. $container->bind('Computer', function($container, $modleName) {
    2. return new Computer($conrainer->make($modleName));
    3. });

    可以看到相对于上述例子,这里最大的变化是在匿名函数里进行了一次 make (创建) 。可以看成 Computer 类是被服务容器来注入的。
    调用也非常简单:

    1. $computer3 = $container->make('Computer', ['Board']);

    一层一层的来分析:
    首先 instances 数组中没有该字符串,直接看下一步。
    由于这里传入了 parameters 数组可以看成这样:[Conrainer 实例,’Board’]。
    故,我们通过 call_user_func_array() 来调用闭包函数时,可以传入两个参数。就和 bind
    函数里绑定的参数一样,$container 对应 Container 实例, $modleName 对应 ‘Board’ 字符串.
    所以对于匿名函数中返回的 Computer

    1. new Computer($container->make($modleName));

    可以看成

    1. new Computer($container->make('Board');

    又是一层 make 函数,和上面一样,这里不再深究。
    所以我们的 $computer3 返回的类型为自动注入了 CommonBoard 的 Computer 类。