当前位置:首页 > 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.   protected $instances = []; 
  5.  
  6.   // 绑定服务 
  7.   public function instance($name$instance
  8.   { 
  9.     $this->instances[$name] = $instance
  10.   } 
  11.  
  12.   // 获取服务 
  13.   public function get($name
  14.   { 
  15.     return isset($this->instances[$name]) ? $this->instances[$name] : null; 
  16.   } 
  17.  
  18. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  19.  
  20. $container = new BaseContainer(); 
  21. // 绑定服务 
  22. $container->instance('StdClass'new StdClass()); 
  23. // 获取服务 
  24. $stdClass = $container->get('StdClass'); 
  25. var_dump($stdClass); 

3. 按需实例化

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

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

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

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

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

6. 自定义依赖参数

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

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

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

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

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

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

7. 服务别名

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

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

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

  1. class AliasContainer extends ParametersContainer 
  2.   // 服务别名 
  3.   protected $aliases = []; 
  4.  
  5.   // 给服务绑定一个别名 
  6.   public function alias($alias$name
  7.   { 
  8.     $this->aliases[$alias] = $name
  9.   } 
  10.  
  11.   // 获取服务 
  12.   public function make($namearray $parameters = []) 
  13.   { 
  14.     // 先用别名查找真实服务名 
  15.     $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name
  16.  
  17.     return parent::make($name$parameters); 
  18.   } 
  19.  
  20. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  21.  
  22. $container = new AliasContainer(); 
  23.  
  24. // 绑定服务 
  25. $container->instance('text''这是一个字符串'); 
  26. // 给服务注册别名 
  27. $container->alias('string''text'); 
  28. $container->alias('content''text'); 
  29.  
  30. var_dump($container->make('string')); 
  31. 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.   protected $extenders = []; 
  4.  
  5.   // 给服务绑定扩展器 
  6.   public function extend($name$extender
  7.   { 
  8.     if (isset($this->instances[$name])) { 
  9.       // 已经实例化的服务,直接调用扩展器 
  10.       $this->instances[$name] = $extender($this->instances[$name]); 
  11.     } else { 
  12.       $this->extenders[$name][] = $extender
  13.     } 
  14.   } 
  15.  
  16.   // 获取服务 
  17.   public function make($namearray $parameters = []) 
  18.   { 
  19.     $instance = parent::make($name$parameters); 
  20.  
  21.     if (isset($this->extenders[$name])) { 
  22.       // 调用扩展器 
  23.       foreach ($this->extenders[$nameas $extender) { 
  24.         $instance = $extender($instance); 
  25.       } 
  26.     } 
  27.  
  28.     return $instance
  29.   } 
  30.  
  31. // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // 
  32.  
  33. class Redis 
  34.   public $name
  35.  
  36.   public function __construct($name = 'default'
  37.   { 
  38.     $this->name = $name
  39.   } 
  40.  
  41.   public function setName($name
  42.   { 
  43.     $this->name = $name
  44.   } 
  45.  
  46. $container = new ExtendContainer(); 
  47.  
  48. // 绑定Redis服务 
  49. $container->singleton(Redis::classfunction () { 
  50.   return new Redis(); 
  51. }); 
  52.  
  53. // 给Redis服务绑定一个扩展器 
  54. $container->extend(Redis::classfunction (Redis $redis) { 
  55.   $redis->setName('扩展器'); 
  56.   return $redis
  57. }); 
  58. $redis = $container->make(Redis::class); 
  59. var_dump($redis->name); 

9. 上下文绑定

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

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

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

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

10. 完整代码

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

Tags: Laravel服务容器

分享到: