当前位置:首页 > CMS教程 > 其它CMS > 列表

详解如何实现Laravel的服务容器的方法示例

发布:smiling 来源: PHP粉丝网  添加日期:2021-11-18 17:34:29 浏览: 评论:0 

这篇文章主要介绍了详解如何实现Laravel的服务容器的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

1. 容器的本质

服务容器本身就是一个数组,键名就是服务名,值就是服务。

服务可以是一个原始值,也可以是一个对象,可以说是任意数据。

服务名可以是自定义名,也可以是对象的类名,也可以是接口名。

  1. // 服务容器 
  2. $container = [ 
  3.   // 原始值 
  4.   'text' => '这是一个字符串', 
  5.   // 自定义服务名 
  6.   'customName' => new StdClass(), 
  7.   // 使用类名作为服务名 
  8.   'StdClass' => new StdClass(), 
  9.   // 使用接口名作为服务名 
  10.   'Namespace\\StdClassInterface' => new StdClass(), 
  11. ]; 
  12.  
  13. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  14.  
  15. // 绑定服务到容器 
  16. $container['standard'] = new StdClass(); 
  17. // 获取服务 
  18. $standard = $container['standard']; 
  19. var_dump($standard); 

2. 封装成类

为了方便维护,我们把上面的数组封装到类里面。

$instances还是上面的容器数组。我们增加两个方法,instance用来绑定服务,get用来从容器中获取服务。

  1. class BaseContainer 
  2. { 
  3.  
  4.   // 已绑定的服务 
  5.   protected $instances = []; 
  6.  
  7.   // 绑定服务 
  8.   public function instance($name, $instance) 
  9.   { 
  10.     $this->instances[$name] = $instance; 
  11.   } 
  12.  
  13.   // 获取服务 
  14.   public function get($name) 
  15.   { 
  16.     return isset($this->instances[$name]) ? $this->instances[$name] : null; 
  17.   } 
  18. } 
  19.  
  20. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  21.  
  22. $container = new BaseContainer(); 
  23. // 绑定服务 
  24. $container->instance('StdClass', new StdClass()); 
  25. // 获取服务 
  26. $stdClass = $container->get('StdClass'); 
  27. var_dump($stdClass); 

3. 按需实例化

现在我们在绑定一个对象服务的时候,就必须要先把类实例化,如果绑定的服务没有被用到,那么类就会白白实例化,造成性能浪费。

为了解决这个问题,我们增加一个bind函数,它支持绑定一个回调函数,在回调函数中实例化类。这样一来,我们只有在使用服务时,才回调这个函数,这样就实现了按需实例化。

这时候,我们获取服务时,就不只是从数组中拿到服务并返回了,还需要判断如果是回调函数,就要执行回调函数。所以我们把get方法的名字改成make。意思就是生产一个服务,这个服务可以是已绑定的服务,也可以是已绑定的回调函数,也可以是一个类名,如果是类名,我们就直接实例化该类并返回。

然后,我们增加一个新数组$bindings,用来存储绑定的回调函数。然后我们把bind方法改一下,判断下$instance如果是一个回调函数,就放到$bindings数组,否则就用make方法实例化类。

  1. class DeferContainer extend BaseContainer 
  2. { 
  3.   // 已绑定的回调函数 
  4.   protected $bindings = []; 
  5.  
  6.   // 绑定服务 
  7.   public function bind($name, $instance) 
  8.   { 
  9.     if ($instance instanceof Closure) { 
  10.       // 如果$instance是一个回调函数,就绑定到bindings。 
  11.       $this->bindings[$name] = $instance; 
  12.     } else { 
  13.       // 调用make方法,创建实例 
  14.       $this->instances[$name] = $this->make($name); 
  15.     } 
  16.   } 
  17.  
  18.   // 获取服务 
  19.   public function make($name) 
  20.   { 
  21.     if (isset($this->instances[$name])) { 
  22.       return $this->instances[$name]; 
  23.     } 
  24.  
  25.     if (isset($this->bindings[$name])) { 
  26.       // 执行回调函数并返回 
  27.       $instance = call_user_func($this->bindings[$name]); 
  28.     } else { 
  29.       // 还没有绑定到容器中,直接new. 
  30.       $instance = new $name(); 
  31.     } 
  32.  
  33.     return $instance; 
  34.   } 
  35. } 
  36.  
  37. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  38.  
  39. $container = new DeferContainer(); 
  40. // 绑定服务 
  41. $container->bind('StdClass', function () { 
  42.   echo "我被执行了\n"; 
  43.   return new StdClass(); 
  44. }); 
  45. // 获取服务 
  46. $stdClass = $container->make('StdClass'); 
  47. var_dump($stdClass); 

StdClass这个服务绑定的是一个回调函数,在回调函数中才会真正的实例化类。如果没有用到这个服务,那回调函数就不会被执行,类也不会被实例化。

4. 单例

从上面的代码中可以看出,每次调用make方法时,都会执行一次回调函数,并返回一个新的类实例。但是在某些情况下,我们希望这个实例是一个单例,无论make多少次,只实例化一次。

这时候,我们给bind方法增加第三个参数$shared,用来标记是否是单例,默认不是单例。然后把回调函数和这个标记都存到$bindings数组里。

为了方便绑定单例服务,再增加一个新的方法singleton,它直接调用bind,并且$shared参数强制为true。

对于make方法,我们也要做修改。在执行$bindings里的回调函数以后,做一个判断,如果之前绑定时标记的shared是true,就把回调函数返回的结果存储到$instances里。由于我们是先从$instances里找服务,所以这样下次再make的时候就会直接返回,而不会再次执行回调函数。这样就实现了单例的绑定。

  1. class SingletonContainer extends DeferContainer 
  2. { 
  3.   // 绑定服务 
  4.   public function bind($name, $instance, $shared = false) 
  5.   { 
  6.     if ($instance instanceof Closure) { 
  7.       // 如果$instance是一个回调函数,就绑定到bindings。 
  8.       $this->bindings[$name] = [ 
  9.         'callback' => $instance, 
  10.         // 标记是否单例 
  11.         'shared' => $shared 
  12.       ]; 
  13.     } else { 
  14.       // 调用make方法,创建实例 
  15.       $this->instances[$name] = $this->make($name); 
  16.     } 
  17.   } 
  18.  
  19.   // 绑定一个单例 
  20.   public function singleton($name, $instance) 
  21.   { 
  22.     $this->bind($name, $instance, true); 
  23.   } 
  24.  
  25.   // 获取服务 
  26.   public function make($name) 
  27.   { 
  28.     if (isset($this->instances[$name])) { 
  29.       return $this->instances[$name]; 
  30.     } 
  31.  
  32.     if (isset($this->bindings[$name])) { 
  33.       // 执行回调函数并返回 
  34.       $instance = call_user_func($this->bindings[$name]['callback']); 
  35.  
  36.       if ($this->bindings[$name]['shared']) { 
  37.         // 标记为单例时,存储到服务中 
  38.         $this->instances[$name] = $instance; 
  39.       } 
  40.     } else { 
  41.       // 还没有绑定到容器中,直接new. 
  42.       $instance = new $name(); 
  43.     } 
  44.  
  45.     return $instance; 
  46.   } 
  47. } 
  48.  
  49. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  50.  
  51. $container = new SingletonContainer(); 
  52. // 绑定服务 
  53. $container->singleton('anonymous', function () { 
  54.   return new class 
  55.   { 
  56.     public function __construct() 
  57.     { 
  58.       echo "我被实例化了\n"; 
  59.     } 
  60.   }; 
  61. }); 
  62. // 无论make多少次,只会实例化一次 
  63. $container->make('anonymous'); 
  64. $container->make('anonymous'); 
  65. // 获取服务 
  66. $anonymous = $container->make('anonymous'); 
  67. var_dump($anonymous) 

上面的代码用singleton绑定了一个名为anonymous的服务,回调函数里返回了一个匿名类的实例。这个匿名类在被实例化时会输出一段文字,无论我们make多少次anonymous,这个回调函数只会被执行一次,匿名类也只会被实例化一次。

5. 自动注入

自动注入是Ioc容器的核心,没有自动注入就无法做到控制反转。

自动注入就是指,在实例化一个类时,用反射类来获取__construct所需要的参数,然后根据参数的类型,从容器中找到已绑定的服务,我们只要有了__construct方法所需的所有参数,就能自动实例化该类,实现自动注入。

现在,我们增加一个build方法,它只接收一个参数,就是类名。build方法会用反射类来获取__construct方法所需要的参数,然后返回实例化结果。

另外一点就是,我们之前在调用make方法时,如果传的是一个未绑定的类,我们直接new了这个类,现在我们把未绑定的类交给build方法来构建,因为它支持自动注入。

  1. class InjectionContainer extends SingletonContainer 
  2. { 
  3.  
  4.   // 获取服务 
  5.   public function make($name) 
  6.   { 
  7.     if (isset($this->instances[$name])) { 
  8.       return $this->instances[$name]; 
  9.     } 
  10.     if (isset($this->bindings[$name])) { 
  11.       // 执行回调函数并返回 
  12.       $instance = call_user_func($this->bindings[$name]['callback']); 
  13.  
  14.       if ($this->bindings[$name]['shared']) { 
  15.         // 标记为单例时,存储到服务中 
  16.         $this->instances[$name] = $instance; 
  17.       } 
  18.     } else { 
  19.       // 使用build方法构建此类 
  20.       $instance = $this->build($name); 
  21.     } 
  22.  
  23.     return $instance; 
  24.   } 
  25.  
  26.   // 构建一个类,并自动注入服务 
  27.   public function build($class) 
  28.   { 
  29.  
  30.     $reflector = new ReflectionClass($class); 
  31.  
  32.     $constructor = $reflector->getConstructor(); 
  33.  
  34.     if (is_null($constructor)) { 
  35.       // 没有构造函数,直接new 
  36.       return new $class(); 
  37.     } 
  38.  
  39.     $dependencies = []; 
  40.  
  41.     // 获取构造函数所需的参数 
  42.     foreach ($constructor->getParameters() as $dependency) { 
  43.       if (is_null($dependency->getClass())) { 
  44.         // 参数类型不是类时,无法从容器中获取依赖 
  45.         if ($dependency->isDefaultValueAvailable()) { 
  46.           // 查找参数的默认值,如果有就使用默认值 
  47.           $dependencies[] = $dependency->getDefaultValue(); 
  48.         } else { 
  49.           // 无法提供类所依赖的参数 
  50.           throw new Exception('找不到依赖参数:' . $dependency->getName()); 
  51.         } 
  52.       } else { 
  53.         // 参数类型是类时,就用make方法构建该类 
  54.         $dependencies[] = $this->make($dependency->getClass()->name); 
  55.       } 
  56.     } 
  57.  
  58.     return $reflector->newInstanceArgs($dependencies); 
  59.   } 
  60. } 
  61.  
  62. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  63.  
  64. class Redis 
  65. { 
  66. } 
  67.  
  68. class Cache 
  69. { 
  70.   protected $redis; 
  71.  
  72.   // 构造函数中依赖Redis服务 
  73.   public function __construct(Redis $redis) 
  74.   { 
  75.     $this->redis = $redis; 
  76.   } 
  77. } 
  78.  
  79. $container = new InjectionContainer(); 
  80. // 绑定Redis服务 
  81. $container->singleton(Redis::class, function () { 
  82.   return new Redis(); 
  83. }); 
  84. // 构建Cache类 
  85. $cache = $container->make(Cache::class); 
  86. var_dump($cache); 

6. 自定义依赖参数

现在有个问题,如果类依赖的参数不是类或接口,只是一个普通变量,这时候就无法从容器中获取依赖参数了,也就无法实例化类了。

那么接下来我们就支持一个新功能,在调用make方法时,支持传第二个参数$parameters,这是一个数组,无法从容器中获取的依赖,就从这个数组中找。

当然,make方法是用不到这个参数的,因为它不负责实例化类,它直接传给build方法。在build方法寻找依赖的参数时,就先从$parameters中找。这样就实现了自定义依赖参数。

需要注意的一点是,build方法是按照参数的名字来找依赖的,所以parameters中的键名也必须跟__construct中参数名一致。

  1. class ParametersContainer extends InjectionContainer 
  2. { 
  3.   // 获取服务 
  4.   public function make($name, array $parameters = []) 
  5.   { 
  6.     if (isset($this->instances[$name])) { 
  7.       return $this->instances[$name]; 
  8.     } 
  9.     if (isset($this->bindings[$name])) { 
  10.       // 执行回调函数并返回 
  11.       $instance = call_user_func($this->bindings[$name]['callback']); 
  12.  
  13.       if ($this->bindings[$name]['shared']) { 
  14.         // 标记为单例时,存储到服务中 
  15.         $this->instances[$name] = $instance; 
  16.       } 
  17.     } else { 
  18.       // 使用build方法构建此类 
  19.       $instance = $this->build($name, $parameters); 
  20.     } 
  21.  
  22.     return $instance; 
  23.   } 
  24.  
  25.   // 构建一个类,并自动注入服务 
  26.   public function build($class, array $parameters = []) 
  27.   { 
  28.     $reflector = new ReflectionClass($class); 
  29.  
  30.     $constructor = $reflector->getConstructor(); 
  31.  
  32.     if (is_null($constructor)) { 
  33.       // 没有构造函数,直接new 
  34.       return new $class(); 
  35.     } 
  36.  
  37.     $dependencies = []; 
  38.  
  39.     // 获取构造函数所需的参数 
  40.     foreach ($constructor->getParameters() as $dependency) { 
  41.  
  42.       if (isset($parameters[$dependency->getName()])) { 
  43.         // 先从自定义参数中查找 
  44.         $dependencies[] = $parameters[$dependency->getName()]; 
  45.         continue; 
  46.       } 
  47.  
  48.       if (is_null($dependency->getClass())) { 
  49.         // 参数类型不是类或接口时,无法从容器中获取依赖 
  50.         if ($dependency->isDefaultValueAvailable()) { 
  51.           // 查找默认值,如果有就使用默认值 
  52.           $dependencies[] = $dependency->getDefaultValue(); 
  53.         } else { 
  54.           // 无法提供类所依赖的参数 
  55.           throw new Exception('找不到依赖参数:' . $dependency->getName()); 
  56.         } 
  57.       } else { 
  58.         // 参数类型是类时,就用make方法构建该类 
  59.         $dependencies[] = $this->make($dependency->getClass()->name); 
  60.       } 
  61.     } 
  62.  
  63.     return $reflector->newInstanceArgs($dependencies); 
  64.   } 
  65. } 
  66.  
  67. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  68.  
  69. class Redis 
  70. { 
  71. } 
  72.  
  73. class Cache 
  74. { 
  75.   protected $redis; 
  76.  
  77.   protected $name; 
  78.  
  79.   protected $default; 
  80.  
  81.   // 构造函数中依赖Redis服务和name参数,name的类型不是类,无法从容器中查找 
  82.   public function __construct(Redis $redis, $name, $default = '默认值') 
  83.   { 
  84.     $this->redis = $redis; 
  85.     $this->name = $name; 
  86.     $this->default = $default; 
  87.   } 
  88. } 
  89.  
  90. $container = new ParametersContainer(); 
  91. // 绑定Redis服务 
  92. $container->singleton(Redis::class, function () { 
  93.   return new Redis(); 
  94. }); 
  95. // 构建Cache类 
  96. $cache = $container->make(Cache::class, ['name' => 'test']); 
  97. var_dump($cache); 

提示:实际上,Laravel容器的build方法并没有第二个参数$parameters,它是用类属性来维护自定义参数。原理都是一样的,只是实现方式不一样。这里为了方便理解,不引入过多概念。

7. 服务别名

别名可以理解成小名、外号。服务别名就是给已绑定的服务设置一些外号,使我们通过外号也能找到该服务。

这个就比较简单了,我们增加一个新的数组$aliases,用来存储别名。再增加一个方法alias,用来让外部注册别名。

唯一需要我们修改的地方,就是在make时,要先从$aliases中找到真实的服务名。

  1. class AliasContainer extends ParametersContainer 
  2. { 
  3.   // 服务别名 
  4.   protected $aliases = []; 
  5.  
  6.   // 给服务绑定一个别名 
  7.   public function alias($alias, $name) 
  8.   { 
  9.     $this->aliases[$alias] = $name; 
  10.   } 
  11.  
  12.   // 获取服务 
  13.   public function make($name, array $parameters = []) 
  14.   { 
  15.     // 先用别名查找真实服务名 
  16.     $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; 
  17.  
  18.     return parent::make($name, $parameters); 
  19.   } 
  20. } 
  21.  
  22. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  23.  
  24. $container = new AliasContainer(); 
  25.  
  26. // 绑定服务 
  27. $container->instance('text', '这是一个字符串'); 
  28. // 给服务注册别名 
  29. $container->alias('string', 'text'); 
  30. $container->alias('content', 'text'); 
  31.  
  32. var_dump($container->make('string')); 
  33. var_dump($container->make('content')); 

8. 扩展绑定

有时候我们需要给已绑定的服务做一个包装,这时候就用到扩展绑定了。我们先看一个实际的用法,理解它的作用后,才看它是如何实现的。

  1. // 绑定日志服务 
  2. $container->singleton('log', new Log()); 
  3.  
  4. // 对已绑定的服务再次包装 
  5. $container->extend('log', function(Log $log){ 
  6.   // 返回了一个新服务 
  7.   return new RedisLog($log); 
  8. }); 

现在我们看它是如何实现的。增加一个$extenders数组,用来存放扩展器。再增加一个extend方法,用来注册扩展器。

然后在make方法返回$instance之前,按顺序依次调用之前注册的扩展器。

  1. class ExtendContainer extends AliasContainer 
  2. { 
  3.   // 存放扩展器的数组 
  4.   protected $extenders = []; 
  5.  
  6.   // 给服务绑定扩展器 
  7.   public function extend($name, $extender) 
  8.   { 
  9.     if (isset($this->instances[$name])) { 
  10.       // 已经实例化的服务,直接调用扩展器 
  11.       $this->instances[$name] = $extender($this->instances[$name]); 
  12.     } else { 
  13.       $this->extenders[$name][] = $extender; 
  14.     } 
  15.   } 
  16.  
  17.   // 获取服务 
  18.   public function make($name, array $parameters = []) 
  19.   { 
  20.     $instance = parent::make($name, $parameters); 
  21.  
  22.     if (isset($this->extenders[$name])) { 
  23.       // 调用扩展器 
  24.       foreach ($this->extenders[$name] as $extender) { 
  25.         $instance = $extender($instance); 
  26.       } 
  27.     } 
  28.  
  29.     return $instance; 
  30.   } 
  31. } 
  32.  
  33. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  34.  
  35. class Redis 
  36. { 
  37.   public $name; 
  38.  
  39.   public function __construct($name = 'default') 
  40.   { 
  41.     $this->name = $name; 
  42.   } 
  43.  
  44.   public function setName($name) 
  45.   { 
  46.     $this->name = $name; 
  47.   } 
  48. } 
  49.  
  50. $container = new ExtendContainer(); 
  51.  
  52. // 绑定Redis服务 
  53. $container->singleton(Redis::class, function () { 
  54.   return new Redis(); 
  55. }); 
  56.  
  57. // 给Redis服务绑定一个扩展器 
  58. $container->extend(Redis::class, function (Redis $redis) { 
  59.   $redis->setName('扩展器'); 
  60.   return $redis; 
  61. }); 
  62. $redis = $container->make(Redis::class); 
  63. var_dump($redis->name); 

9. 上下文绑定

有时侯我们可能有两个类使用同一个接口,但希望在每个类中注入不同的实现,例如两个控制器,分别为它们注入不同的Log服务。

  1. class ApiController 
  2. { 
  3.   public function __construct(Log $log) 
  4.   { 
  5.   } 
  6. } 
  7.  
  8. class WebController 
  9. { 
  10.   public function __construct(Log $log) 
  11.   { 
  12.   } 
  13. } 

最终我们要用以下方式实现:

  1. // 当ApiController依赖Log时,给它一个RedisLog 
  2. $container->addContextualBinding('ApiController','Log',new RedisLog()); 
  3.  
  4. // 当WebController依赖Log时,给它一个FileLog 
  5. $container->addContextualBinding('WebController','Log',new FileLog()); 

为了更直观更方便更语义化的使用,我们把这个过程改成链式操作:

  1. $container->when('ApiController') 
  2.     ->needs('Log') 
  3.     ->give(new RedisLog()); 

我们增加一个$context数组,用来存储上下文,同时增加一个addContextualBinding方法,用来注册上下文绑定,以ApiController为例,$context的真实模样是:

$context['ApiController']['Log'] = new RedisLog();

然后build方法实例化类时,先从上下文中查找依赖参数,就实现了上下文绑定。

接下来,看看链式操作是如何实现的。

首先定义一个类Context,这个类有两个方法,needs和give。

然后在容器中,增加一个when方法,它返回一个Context对象。在Context对象的give方法中,我们已经具备了注册上下文所需要的所有参数,所以就可以在give方法中调用addContextualBinding来注册上下文了。

  1. class ContextContainer extends ExtendContainer 
  2. { 
  3.   // 依赖上下文 
  4.   protected $context = []; 
  5.  
  6.   // 构建一个类,并自动注入服务 
  7.   public function build($class, array $parameters = []) 
  8.   { 
  9.     $reflector = new ReflectionClass($class); 
  10.  
  11.     $constructor = $reflector->getConstructor(); 
  12.  
  13.     if (is_null($constructor)) { 
  14.       // 没有构造函数,直接new 
  15.       return new $class(); 
  16.     } 
  17.  
  18.     $dependencies = []; 
  19.  
  20.     // 获取构造函数所需的参数 
  21.     foreach ($constructor->getParameters() as $dependency) { 
  22.  
  23.       if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) { 
  24.         // 先从上下文中查找 
  25.         $dependencies[] = $this->context[$class][$dependency->getName()]; 
  26.         continue; 
  27.       } 
  28.  
  29.       if (isset($parameters[$dependency->getName()])) { 
  30.         // 从自定义参数中查找 
  31.         $dependencies[] = $parameters[$dependency->getName()]; 
  32.         continue; 
  33.       } 
  34.  
  35.       if (is_null($dependency->getClass())) { 
  36.         // 参数类型不是类或接口时,无法从容器中获取依赖 
  37.         if ($dependency->isDefaultValueAvailable()) { 
  38.           // 查找默认值,如果有就使用默认值 
  39.           $dependencies[] = $dependency->getDefaultValue(); 
  40.         } else { 
  41.           // 无法提供类所依赖的参数 
  42.           throw new Exception('找不到依赖参数:' . $dependency->getName()); 
  43.         } 
  44.       } else { 
  45.         // 参数类型是一个类时,就用make方法构建该类 
  46.         $dependencies[] = $this->make($dependency->getClass()->name); 
  47.       } 
  48.     } 
  49.  
  50.     return $reflector->newInstanceArgs($dependencies); 
  51.   } 
  52.  
  53.   // 绑定上下文 
  54.   public function addContextualBinding($when, $needs, $give) 
  55.   { 
  56.     $this->context[$when][$needs] = $give; 
  57.   } 
  58.  
  59.   // 支持链式方式绑定上下文 
  60.   public function when($when) 
  61.   { 
  62.     return new Context($when, $this); 
  63.   } 
  64. } 
  65.  
  66. class Context 
  67. { 
  68.   protected $when; 
  69.  
  70.   protected $needs; 
  71.  
  72.   protected $container; 
  73.  
  74.   public function __construct($when, ContextContainer $container) 
  75.   { 
  76.     $this->when = $when; 
  77.     $this->container = $container; 
  78.   } 
  79.  
  80.   public function needs($needs) 
  81.   { 
  82.     $this->needs = $needs; 
  83.  
  84.     return $this; 
  85.   } 
  86.  
  87.   public function give($give) 
  88.   { 
  89.     // 调用容器绑定依赖上下文 
  90.     $this->container->addContextualBinding($this->when, $this->needs, $give); 
  91.   } 
  92. } 
  93.  
  94. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  95.  
  96. class Dog 
  97. { 
  98.   public $name; 
  99.  
  100.   public function __construct($name) 
  101.   { 
  102.     $this->name = $name; 
  103.   } 
  104. } 
  105.  
  106. class Cat 
  107. { 
  108.   public $name; 
  109.  
  110.   public function __construct($name) 
  111.   { 
  112.     $this->name = $name; 
  113.   } 
  114. } 
  115.  
  116. $container = new ContextContainer(); 
  117.  
  118. // 给Dog类设置上下文绑定 
  119. $container->when(Dog::class) 
  120.   ->needs('name') 
  121.   ->give('小狗'); 
  122. // 给Cat类设置上下文绑定 
  123. $container->when(Cat::class) 
  124.   ->needs('name') 
  125.   ->give('小猫'); 
  126.  
  127. $dog = $container->make(Dog::class); 
  128. $cat = $container->make(Cat::class); 
  129. var_dump('Dog:' . $dog->name); 
  130. var_dump('Cat:' . $cat->name); 

10. 完整代码

  1. class Container 
  2. { 
  3.   // 已绑定的服务 
  4.   protected $instances = []; 
  5.   // 已绑定的回调函数 
  6.   protected $bindings = []; 
  7.   // 服务别名 
  8.   protected $aliases = []; 
  9.   // 存放扩展器的数组 
  10.   protected $extenders = []; 
  11.   // 依赖上下文 
  12.   protected $context = []; 
  13.  
  14.   // 绑定服务实例 
  15.   public function instance($name, $instance) 
  16.   { 
  17.     $this->instances[$name] = $instance; 
  18.   } 
  19.  
  20.   // 绑定服务 
  21.   public function bind($name, $instance, $shared = false) 
  22.   { 
  23.     if ($instance instanceof Closure) { 
  24.       // 如果$instance是一个回调函数,就绑定到bindings。 
  25.       $this->bindings[$name] = [ 
  26.         'callback' => $instance, 
  27.         // 标记是否单例 
  28.         'shared' => $shared 
  29.       ]; 
  30.     } else { 
  31.       // 调用make方法,创建实例 
  32.       $this->instances[$name] = $this->make($name); 
  33.     } 
  34.   } 
  35.  
  36.   // 绑定一个单例 
  37.   public function singleton($name, $instance) 
  38.   { 
  39.     $this->bind($name, $instance, true); 
  40.   } 
  41.  
  42.   // 给服务绑定一个别名 
  43.   public function alias($alias, $name) 
  44.   { 
  45.     $this->aliases[$alias] = $name; 
  46.   } 
  47.  
  48.   // 给服务绑定扩展器 
  49.   public function extend($name, $extender) 
  50.   { 
  51.     if (isset($this->instances[$name])) { 
  52.       // 已经实例化的服务,直接调用扩展器 
  53.       $this->instances[$name] = $extender($this->instances[$name]); 
  54.     } else { 
  55.       $this->extenders[$name][] = $extender; 
  56.     } 
  57.   } 
  58.  
  59.   // 获取服务 
  60.   public function make($name, array $parameters = []) 
  61.   { 
  62.     // 先用别名查找真实服务名 
  63.     $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; 
  64.  
  65.     if (isset($this->instances[$name])) { 
  66.       return $this->instances[$name]; 
  67.     } 
  68.  
  69.     if (isset($this->bindings[$name])) { 
  70.       // 执行回调函数并返回 
  71.       $instance = call_user_func($this->bindings[$name]['callback']); 
  72.  
  73.       if ($this->bindings[$name]['shared']) { 
  74.         // 标记为单例时,存储到服务中 
  75.         $this->instances[$name] = $instance; 
  76.       } 
  77.     } else { 
  78.       // 使用build方法构建此类 
  79.       $instance = $this->build($name, $parameters); 
  80.     } 
  81.  
  82.     if (isset($this->extenders[$name])) { 
  83.       // 调用扩展器 
  84.       foreach ($this->extenders[$name] as $extender) { 
  85.         $instance = $extender($instance); 
  86.       } 
  87.     } 
  88.  
  89.     return $instance; 
  90.   } 
  91.  
  92.   // 构建一个类,并自动注入服务 
  93.   public function build($class, array $parameters = []) 
  94.   { 
  95.     $reflector = new ReflectionClass($class); 
  96.  
  97.     $constructor = $reflector->getConstructor(); 
  98.  
  99.     if (is_null($constructor)) { 
  100.       // 没有构造函数,直接new 
  101.       return new $class(); 
  102.     } 
  103.  
  104.     $dependencies = []; 
  105.  
  106.     // 获取构造函数所需的参数 
  107.     foreach ($constructor->getParameters() as $dependency) { 
  108.  
  109.       if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) { 
  110.         // 先从上下文中查找 
  111.         $dependencies[] = $this->context[$class][$dependency->getName()]; 
  112.         continue; 
  113.       } 
  114.  
  115.       if (isset($parameters[$dependency->getName()])) { 
  116.         // 从自定义参数中查找 
  117.         $dependencies[] = $parameters[$dependency->getName()]; 
  118.         continue; 
  119.       } 
  120.  
  121.       if (is_null($dependency->getClass())) { 
  122.         // 参数类型不是类或接口时,无法从容器中获取依赖 
  123.         if ($dependency->isDefaultValueAvailable()) { 
  124.           // 查找默认值,如果有就使用默认值 
  125.           $dependencies[] = $dependency->getDefaultValue(); 
  126.         } else { 
  127.           // 无法提供类所依赖的参数 
  128.           throw new Exception('找不到依赖参数:' . $dependency->getName()); 
  129.         } 
  130.       } else { 
  131.         // 参数类型是一个类时,就用make方法构建该类 
  132.         $dependencies[] = $this->make($dependency->getClass()->name); 
  133.       } 
  134.     } 
  135.  
  136.     return $reflector->newInstanceArgs($dependencies); 
  137.   } 
  138.  
  139.   // 绑定上下文 
  140.   public function addContextualBinding($when, $needs, $give) 
  141.   { 
  142.     $this->context[$when][$needs] = $give; 
  143.   } 
  144.  
  145.   // 支持链式方式绑定上下文 
  146.   public function when($when) 
  147.   { 
  148.     return new Context($when, $this); 
  149.   } 
  150. } 
  151.  
  152. class Context 
  153. { 
  154.   protected $when; 
  155.  
  156.   protected $needs; 
  157.  
  158.   protected $container; 
  159.  
  160.   public function __construct($when, Container $container) 
  161.   { 
  162.     $this->when = $when; 
  163.     $this->container = $container; 
  164.   } 
  165.  
  166.   public function needs($needs) 
  167.   { 
  168.     $this->needs = $needs; 
  169.  
  170.     return $this; 
  171.   } 
  172.  
  173.   public function give($give) 
  174.   { 
  175.     // 调用容器绑定依赖上下文 
  176.     $this->container->addContextualBinding($this->when, $this->needs, $give); 
  177.   } 
  178. }

Tags: Laravel服务容器

分享到: