- 服务容器自己就是一个数组,键名就是服务名,值就是服务。
- 服务能够是一个原始值,也能够是一个对象,能够说是任意数据。
- 服务名能够是自定义名,也能够是对象的类名,也能够是接口名。
// 服务容器 $container = [ // 原始值 'text' => '这是一个字符串', // 自定义服务名 'customName' => new StdClass(), // 使用类名做为服务名 'StdClass' => new StdClass(), // 使用接口名做为服务名 'Namespace\\StdClassInterface' => new StdClass(), ]; // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // // 绑定服务到容器 $container['standard'] = new StdClass(); // 获取服务 $standard = $container['standard']; var_dump($standard);
为了方便维护,咱们把上面的数组封装到类里面。php
$instances
仍是上面的容器数组。咱们增长两个方法,instance
用来绑定服务,get
用来从容器中获取服务。redis
class BaseContainer { // 已绑定的服务 protected $instances = []; // 绑定服务 public function instance($name, $instance) { $this->instances[$name] = $instance; } // 获取服务 public function get($name) { return isset($this->instances[$name]) ? $this->instances[$name] : null; } } // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // $container = new BaseContainer(); // 绑定服务 $container->instance('StdClass', new StdClass()); // 获取服务 $stdClass = $container->get('StdClass'); var_dump($stdClass);
如今咱们在绑定一个对象服务的时候,就必需要先把类实例化,若是绑定的服务没有被用到,那么类就会白白实例化,形成性能浪费。数组
为了解决这个问题,咱们增长一个bind
函数,它支持绑定一个回调函数,在回调函数中实例化类。这样一来,咱们只有在使用服务时,才回调这个函数,这样就实现了按需实例化。函数
这时候,咱们获取服务时,就不仅是从数组中拿到服务并返回了,还须要判断若是是回调函数,就要执行回调函数。因此咱们把get
方法的名字改为make
。意思就是生产一个服务,这个服务能够是已绑定的服务,也能够是已绑定的回调函数,也能够是一个类名,若是是类名,咱们就直接实例化该类并返回。性能
而后,咱们增长一个新数组$bindings
,用来存储绑定的回调函数。而后咱们把bind
方法改一下,判断下$instance
若是是一个回调函数,就放到$bindings
数组,不然就用make
方法实例化类。ui
class DeferContainer extends BaseContainer { // 已绑定的回调函数 protected $bindings = []; // 绑定服务 public function bind($name, $instance) { if ($instance instanceof Closure) { // 若是$instance是一个回调函数,就绑定到bindings。 $this->bindings[$name] = $instance; } else { // 调用make方法,建立实例 $this->instances[$name] = $this->make($name); } } // 获取服务 public function make($name) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name]); } else { // 尚未绑定到容器中,直接new. $instance = new $name(); } return $instance; } } // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // $container = new DeferContainer(); // 绑定服务 $container->bind('StdClass', function () { echo "我被执行了\n"; return new StdClass(); }); // 获取服务 $stdClass = $container->make('StdClass'); var_dump($stdClass);
StdClass
这个服务绑定的是一个回调函数,在回调函数中才会真正的实例化类。若是没有用到这个服务,那回调函数就不会被执行,类也不会被实例化。this
从上面的代码中能够看出,每次调用make
方法时,都会执行一次回调函数,并返回一个新的类实例。可是在某些状况下,咱们但愿这个实例是一个单例,不管make
多少次,只实例化一次。spa
这时候,咱们给bind
方法增长第三个参数$shared
,用来标记是不是单例,默认不是单例。而后把回调函数和这个标记都存到$bindings
数组里。日志
为了方便绑定单例服务,再增长一个新的方法singleton
,它直接调用bind
,而且$shared
参数强制为true
。code
对于make
方法,咱们也要作修改。在执行$bindings
里的回调函数之后,作一个判断,若是以前绑定时标记的shared
是true
,就把回调函数返回的结果存储到$instances
里。因为咱们是先从$instances
里找服务,因此这样下次再make
的时候就会直接返回,而不会再次执行回调函数。这样就实现了单例的绑定。
class SingletonContainer extends DeferContainer { // 绑定服务 public function bind($name, $instance, $shared = false) { if ($instance instanceof Closure) { // 若是$instance是一个回调函数,就绑定到bindings。 $this->bindings[$name] = [ 'callback' => $instance, // 标记是否单例 'shared' => $shared ]; } else { // 调用make方法,建立实例 $this->instances[$name] = $this->make($name); } } // 绑定一个单例 public function singleton($name, $instance) { $this->bind($name, $instance, true); } // 获取服务 public function make($name) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name]['callback']); if ($this->bindings[$name]['shared']) { // 标记为单例时,存储到服务中 $this->instances[$name] = $instance; } } else { // 尚未绑定到容器中,直接new. $instance = new $name(); } return $instance; } } // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // $container = new SingletonContainer(); // 绑定服务 $container->singleton('anonymous', function () { return new class { public function __construct() { echo "我被实例化了\n"; } }; }); // 不管make多少次,只会实例化一次 $container->make('anonymous'); $container->make('anonymous'); // 获取服务 $anonymous = $container->make('anonymous'); var_dump($anonymous)
上面的代码用singleton
绑定了一个名为anonymous
的服务,回调函数里返回了一个匿名类的实例。这个匿名类在被实例化时会输出一段文字。不管咱们make
多少次anonymous
,这个回调函数只会被执行一次,匿名类也只会被实例化一次。
自动注入是Ioc容器的核心,没有自动注入就没法作到控制反转。
自动注入就是指,在实例化一个类时,用反射类来获取__construct
所须要的参数,而后根据参数的类型,从容器中找到已绑定的服务。咱们只要有了__construct
方法所需的全部参数,就能自动实例化该类,实现自动注入。
如今,咱们增长一个build
方法,它只接收一个参数,就是类名。build
方法会用反射类来获取__construct
方法所须要的参数,而后返回实例化结果。
另一点就是,咱们以前在调用make
方法时,若是传的是一个未绑定的类,咱们直接new了这个类。如今咱们把未绑定的类交给build
方法来构建,由于它支持自动注入。
class InjectionContainer extends SingletonContainer { // 获取服务 public function make($name) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name]['callback']); if ($this->bindings[$name]['shared']) { // 标记为单例时,存储到服务中 $this->instances[$name] = $instance; } } else { // 使用build方法构建此类 $instance = $this->build($name); } return $instance; } // 构建一个类,并自动注入服务 public function build($class) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 没有构造函数,直接new return new $class(); } $dependencies = []; // 获取构造函数所需的参数 foreach ($constructor->getParameters() as $dependency) { if (is_null($dependency->getClass())) { // 参数类型不是类时,没法从容器中获取依赖 if ($dependency->isDefaultValueAvailable()) { // 查找参数的默认值,若是有就使用默认值 $dependencies[] = $dependency->getDefaultValue(); } else { // 没法提供类所依赖的参数 throw new Exception('找不到依赖参数:' . $dependency->getName()); } } else { // 参数类型是类时,就用make方法构建该类 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } } // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // class Redis { } class Cache { protected $redis; // 构造函数中依赖Redis服务 public function __construct(Redis $redis) { $this->redis = $redis; } } $container = new InjectionContainer(); // 绑定Redis服务 $container->singleton(Redis::class, function () { return new Redis(); }); // 构建Cache类 $cache = $container->make(Cache::class); var_dump($cache);
如今有个问题,若是类依赖的参数不是类或接口,只是一个普通变量,这时候就没法从容器中获取依赖参数了,也就没法实例化类了。
那么接下来咱们就支持一个新功能,在调用make
方法时,支持传第二个参数$parameters
,这是一个数组,没法从容器中获取的依赖,就从这个数组中找。
固然,make
方法是用不到这个参数的,由于它不负责实例化类,它直接传给build
方法。在build
方法寻找依赖的参数时,就先从$parameters
中找。这样就实现了自定义依赖参数。
须要注意的一点是,build
方法是按照参数的名字来找依赖的,因此parameters
中的键名也必须跟__construct
中参数名一致。
class ParametersContainer extends InjectionContainer { // 获取服务 public function make($name, array $parameters = []) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name]['callback']); if ($this->bindings[$name]['shared']) { // 标记为单例时,存储到服务中 $this->instances[$name] = $instance; } } else { // 使用build方法构建此类 $instance = $this->build($name, $parameters); } return $instance; } // 构建一个类,并自动注入服务 public function build($class, array $parameters = []) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 没有构造函数,直接new return new $class(); } $dependencies = []; // 获取构造函数所需的参数 foreach ($constructor->getParameters() as $dependency) { if (isset($parameters[$dependency->getName()])) { // 先从自定义参数中查找 $dependencies[] = $parameters[$dependency->getName()]; continue; } if (is_null($dependency->getClass())) { // 参数类型不是类或接口时,没法从容器中获取依赖 if ($dependency->isDefaultValueAvailable()) { // 查找默认值,若是有就使用默认值 $dependencies[] = $dependency->getDefaultValue(); } else { // 没法提供类所依赖的参数 throw new Exception('找不到依赖参数:' . $dependency->getName()); } } else { // 参数类型是类时,就用make方法构建该类 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } } // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // class Redis { } class Cache { protected $redis; protected $name; protected $default; // 构造函数中依赖Redis服务和name参数,name的类型不是类,没法从容器中查找 public function __construct(Redis $redis, $name, $default = '默认值') { $this->redis = $redis; $this->name = $name; $this->default = $default; } } $container = new ParametersContainer(); // 绑定Redis服务 $container->singleton(Redis::class, function () { return new Redis(); }); // 构建Cache类 $cache = $container->make(Cache::class, ['name' => 'test']); var_dump($cache);
提示:实际上,Laravel容器的build
方法并无第二个参数$parameters
,它是用类属性来维护自定义参数。原理都是同样的,只是实现方式不同。这里为了方便理解,不引入过多概念。
别名能够理解成小名
、外号
。服务别名就是给已绑定的服务设置一些外号
,使咱们经过外号
也能找到该服务。
这个就比较简单了,咱们增长一个新的数组$aliases
,用来存储别名。再增长一个方法alias
,用来让外部注册别名。
惟一须要咱们修改的地方,就是在make
时,要先从$aliases
中找到真实的服务名。
class AliasContainer extends ParametersContainer { // 服务别名 protected $aliases = []; // 给服务绑定一个别名 public function alias($alias, $name) { $this->aliases[$alias] = $name; } // 获取服务 public function make($name, array $parameters = []) { // 先用别名查找真实服务名 $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; return parent::make($name, $parameters); } } // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // $container = new AliasContainer(); // 绑定服务 $container->instance('text', '这是一个字符串'); // 给服务注册别名 $container->alias('string', 'text'); $container->alias('content', 'text'); var_dump($container->make('string')); var_dump($container->make('content'));
有时候咱们须要给已绑定的服务作一个包装,这时候就用到扩展绑定了。咱们先看一个实际的用法,理解它的做用后,才看它是如何实现的。
// 绑定日志服务 $container->singleton('log', new Log()); // 对已绑定的服务再次包装 $container->extend('log', function(Log $log){ // 返回了一个新服务 return new RedisLog($log); });
如今咱们看它是如何实现的。增长一个$extenders
数组,用来存放扩展器。再增长一个extend
方法,用来注册扩展器。
而后在make
方法返回$instance
以前,按顺序依次调用以前注册的扩展器。
class ExtendContainer extends AliasContainer { // 存放扩展器的数组 protected $extenders = []; // 给服务绑定扩展器 public function extend($name, $extender) { if (isset($this->instances[$name])) { // 已经实例化的服务,直接调用扩展器 $this->instances[$name] = $extender($this->instances[$name]); } else { $this->extenders[$name][] = $extender; } } // 获取服务 public function make($name, array $parameters = []) { $instance = parent::make($name, $parameters); if (isset($this->extenders[$name])) { // 调用扩展器 foreach ($this->extenders[$name] as $extender) { $instance = $extender($instance); } } return $instance; } } // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // class Redis { public $name; public function __construct($name = 'default') { $this->name = $name; } public function setName($name) { $this->name = $name; } } $container = new ExtendContainer(); // 绑定Redis服务 $container->singleton(Redis::class, function () { return new Redis(); }); // 给Redis服务绑定一个扩展器 $container->extend(Redis::class, function (Redis $redis) { $redis->setName('扩展器'); return $redis; }); $redis = $container->make(Redis::class); var_dump($redis->name);
有时侯咱们可能有两个类使用同一个接口,但但愿在每一个类中注入不一样的实现,例如两个控制器,分别为它们注入不一样的Log
服务。
class ApiController { public function __construct(Log $log) { } } class WebController { public function __construct(Log $log) { } }
最终咱们要用如下方式实现:
// 当ApiController依赖Log时,给它一个RedisLog $container->addContextualBinding('ApiController','Log',new RedisLog()); // 当WebController依赖Log时,给它一个FileLog $container->addContextualBinding('WebController','Log',new FileLog());
为了更直观更方便更语义化的使用,咱们把这个过程改为链式操做:
$container->when('ApiController') ->needs('Log') ->give(new RedisLog());
咱们增长一个$context
数组,用来存储上下文。同时增长一个addContextualBinding
方法,用来注册上下文绑定。以ApiController
为例,$context
的真实模样是:
$context['ApiController']['Log'] = new RedisLog();
而后build
方法实例化类时,先从上下文中查找依赖参数,就实现了上下文绑定。
接下来,看看链式操做是如何实现的。
首先定义一个类Context
,这个类有两个方法,needs
和give
。
而后在容器中,增长一个when
方法,它返回一个Context
对象。在Context
对象的give
方法中,咱们已经具有了注册上下文所须要的全部参数,因此就能够在give
方法中调用addContextualBinding
来注册上下文了。
class ContextContainer extends ExtendContainer { // 依赖上下文 protected $context = []; // 构建一个类,并自动注入服务 public function build($class, array $parameters = []) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 没有构造函数,直接new return new $class(); } $dependencies = []; // 获取构造函数所需的参数 foreach ($constructor->getParameters() as $dependency) { if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) { // 先从上下文中查找 $dependencies[] = $this->context[$class][$dependency->getName()]; continue; } if (isset($parameters[$dependency->getName()])) { // 从自定义参数中查找 $dependencies[] = $parameters[$dependency->getName()]; continue; } if (is_null($dependency->getClass())) { // 参数类型不是类或接口时,没法从容器中获取依赖 if ($dependency->isDefaultValueAvailable()) { // 查找默认值,若是有就使用默认值 $dependencies[] = $dependency->getDefaultValue(); } else { // 没法提供类所依赖的参数 throw new Exception('找不到依赖参数:' . $dependency->getName()); } } else { // 参数类型是一个类时,就用make方法构建该类 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } // 绑定上下文 public function addContextualBinding($when, $needs, $give) { $this->context[$when][$needs] = $give; } // 支持链式方式绑定上下文 public function when($when) { return new Context($when, $this); } } class Context { protected $when; protected $needs; protected $container; public function __construct($when, ContextContainer $container) { $this->when = $when; $this->container = $container; } public function needs($needs) { $this->needs = $needs; return $this; } public function give($give) { // 调用容器绑定依赖上下文 $this->container->addContextualBinding($this->when, $this->needs, $give); } } // ----------- ↓↓↓↓示例代码↓↓↓↓ ----------- // class Dog { public $name; public function __construct($name) { $this->name = $name; } } class Cat { public $name; public function __construct($name) { $this->name = $name; } } $container = new ContextContainer(); // 给Dog类设置上下文绑定 $container->when(Dog::class) ->needs('name') ->give('小狗'); // 给Cat类设置上下文绑定 $container->when(Cat::class) ->needs('name') ->give('小猫'); $dog = $container->make(Dog::class); $cat = $container->make(Cat::class); var_dump('Dog:' . $dog->name); var_dump('Cat:' . $cat->name);
class Container { // 已绑定的服务 protected $instances = []; // 已绑定的回调函数 protected $bindings = []; // 服务别名 protected $aliases = []; // 存放扩展器的数组 protected $extenders = []; // 依赖上下文 protected $context = []; // 绑定服务实例 public function instance($name, $instance) { $this->instances[$name] = $instance; } // 绑定服务 public function bind($name, $instance, $shared = false) { if ($instance instanceof Closure) { // 若是$instance是一个回调函数,就绑定到bindings。 $this->bindings[$name] = [ 'callback' => $instance, // 标记是否单例 'shared' => $shared ]; } else { // 调用make方法,建立实例 $this->instances[$name] = $this->make($name); } } // 绑定一个单例 public function singleton($name, $instance) { $this->bind($name, $instance, true); } // 给服务绑定一个别名 public function alias($alias, $name) { $this->aliases[$alias] = $name; } // 给服务绑定扩展器 public function extend($name, $extender) { if (isset($this->instances[$name])) { // 已经实例化的服务,直接调用扩展器 $this->instances[$name] = $extender($this->instances[$name]); } else { $this->extenders[$name][] = $extender; } } // 获取服务 public function make($name, array $parameters = []) { // 先用别名查找真实服务名 $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name]['callback']); if ($this->bindings[$name]['shared']) { // 标记为单例时,存储到服务中 $this->instances[$name] = $instance; } } else { // 使用build方法构建此类 $instance = $this->build($name, $parameters); } if (isset($this->extenders[$name])) { // 调用扩展器 foreach ($this->extenders[$name] as $extender) { $instance = $extender($instance); } } return $instance; } // 构建一个类,并自动注入服务 public function build($class, array $parameters = []) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 没有构造函数,直接new return new $class(); } $dependencies = []; // 获取构造函数所需的参数 foreach ($constructor->getParameters() as $dependency) { if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) { // 先从上下文中查找 $dependencies[] = $this->context[$class][$dependency->getName()]; continue; } if (isset($parameters[$dependency->getName()])) { // 从自定义参数中查找 $dependencies[] = $parameters[$dependency->getName()]; continue; } if (is_null($dependency->getClass())) { // 参数类型不是类或接口时,没法从容器中获取依赖 if ($dependency->isDefaultValueAvailable()) { // 查找默认值,若是有就使用默认值 $dependencies[] = $dependency->getDefaultValue(); } else { // 没法提供类所依赖的参数 throw new Exception('找不到依赖参数:' . $dependency->getName()); } } else { // 参数类型是一个类时,就用make方法构建该类 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } // 绑定上下文 public function addContextualBinding($when, $needs, $give) { $this->context[$when][$needs] = $give; } // 支持链式方式绑定上下文 public function when($when) { return new Context($when, $this); } } class Context { protected $when; protected $needs; protected $container; public function __construct($when, Container $container) { $this->when = $when; $this->container = $container; } public function needs($needs) { $this->needs = $needs; return $this; } public function give($give) { // 调用容器绑定依赖上下文 $this->container->addContextualBinding($this->when, $this->needs, $give); } }